After running the demultiplexer_for_dada2 (http://github.com/ramongallego/demultiplexer_for_dada2), we have to denoise the whole dataset. We will do this by using 4 different processes:

As with everything, we will start the process by loading the required packages and datasets.

Load the dataset and metadata

We will load the ASV table and the metadata file. They are in the same folder so we use list.files to access them and a neat combination of bind.rows and map(read_csv)




all.asvs     <- list.files (path ="..", pattern = "^ASV_table_r", recursive = T, full.names = T)
all.metadata <- list.files (path ="..", pattern = "^metadata_r", recursive = T, full.names = T)
all.hashes   <- list.files (path ="..", pattern = "^Hash_key_r", recursive = T, full.names = T,ignore.case = T)

ASV.table <- bind_rows(map(all.asvs, read_csv), .id = "Miseq_run")
Parsed with column specification:
cols(
  sample = col_character(),
  Hash = col_character(),
  nReads = col_double()
)
Parsed with column specification:
cols(
  sample = col_character(),
  Hash = col_character(),
  nReads = col_double()
)
Parsed with column specification:
cols(
  sample = col_character(),
  Hash = col_character(),
  nReads = col_double()
)
Parsed with column specification:
cols(
  sample = col_character(),
  Hash = col_character(),
  nReads = col_double()
)
Parsed with column specification:
cols(
  sample = col_character(),
  Hash = col_character(),
  nReads = col_double()
)
metadata <- bind_rows(map(all.metadata, function(x) {
  read_csv(x) %>%
    dplyr::select("sample_id", "pri_index_name", "Tag"= "sec_index_name")
  }),.id = "Miseq_run")
Parsed with column specification:
cols(
  .default = col_character(),
  distance = col_double(),
  insert_size = col_double(),
  sec_index_start = col_double()
)
See spec(...) for full column specifications.
Parsed with column specification:
cols(
  .default = col_character(),
  distance = col_double(),
  well_location = col_logical(),
  insert_size = col_double(),
  sec_index_start = col_double()
)
See spec(...) for full column specifications.
Parsed with column specification:
cols(
  .default = col_character(),
  distance = col_double(),
  well_location = col_logical(),
  insert_size = col_double(),
  sec_index_start = col_double()
)
See spec(...) for full column specifications.
Parsed with column specification:
cols(
  .default = col_character(),
  distance = col_double(),
  well_location = col_logical(),
  insert_size = col_double(),
  sec_index_start = col_double()
)
See spec(...) for full column specifications.
Parsed with column specification:
cols(
  .default = col_character(),
  distance = col_double(),
  well_location = col_logical(),
  insert_size = col_double(),
  sec_index_start = col_double()
)
See spec(...) for full column specifications.
Hash.key <- bind_rows(map(all.hashes, read_csv))
Parsed with column specification:
cols(
  Hash = col_character(),
  Sequence = col_character()
)
Parsed with column specification:
cols(
  Hash = col_character(),
  Sequence = col_character()
)
Parsed with column specification:
cols(
  Hash = col_character(),
  Sequence = col_character()
)
Parsed with column specification:
cols(
  Hash = col_character(),
  Sequence = col_character()
)
Parsed with column specification:
cols(
  Hash = col_character(),
  Sequence = col_character()
)
Hash.key %>% 
  distinct(Hash, .keep_all = T) -> Hash.key

Data Cleanup - Don’t act like you don’t need this

Emily already cleaneup up the data. A few things we check for: That no sample appears twice in the metadata. That the metadata uses Tag_01 instead of Tag_1 (so it can be sorted alphabetically). That the structure Site_YYYYMM[A-C].[1-3] is the same across the dataset.


# Check that no sample appears more than once in the metadata

metadata %>% 
  group_by(sample_id) %>%
  summarise(tot = n()) %>% 
  arrange(desc(tot)) # Samples only appear once


# We should change Tag_1 for Tag_01

metadata %>%
  mutate(Tag = case_when(str_detect(Tag, "\\_[0-9]{1}$")       ~     str_replace(Tag, "Tag_", "Tag_0"),
                         TRUE                                  ~     Tag  )) -> metadata



metadata %>% mutate(x= str_count(sample_id, pattern = "_")) %>% arrange(desc(x)) # Max number of underscores is 5

metadata %>% mutate(x= str_count(sample_id, pattern = "_")) %>% arrange((x)) # Min number of underscores is also 5

   
ASV.table %>% distinct(sample) %>% mutate(x= str_count(sample, pattern = "_")) %>% arrange(desc(x)) # Also in the ASV table

ASV.table %>% distinct(sample) %>% mutate(x= str_count(sample, pattern = "_")) %>% arrange((x)) # Also in the ASV table
NA

Now let’s check the positive controls: are all for each run kangaroos or also ostriches


metadata %>% 
  filter(str_detect(sample_id, "NA"))
NA

Looking at the metadata - it used Ostriches in Round 1 and Kangaroo in rounds 2-5

The output of this process are a clean ASV table and a clean metadata file.

Cleaning Process 1: Estimation of Tag-jumping or sample cross-talk

Before we modify our datasets on any way, we can calculate how many sequences that were only supposed to be in the positives control appeared in the environmental samples, and how many did the opposite. First we divide the dataset into positive control and environmental samples. Also create an ordered list of the Hashes present in the positive controls, for ease of plotting


ASV.table %>%  mutate(source = case_when(str_detect(sample, "NA_")    ~   "Positives",
                                         TRUE                                                ~   "Samples")) -> ASV.table

ASV.table %>% 
  filter (source == "Positives") %>% 
  group_by(Hash) %>% 
  summarise(tot = sum(nReads)) %>% 
  arrange(desc(tot)) %>% 
  pull(Hash) -> list.of.hashes.in.positive

Now let’s create a jumping vector. What proportion of the reads found in the positives control came from elsewhere?, and what proportion of the reads in the samples came from the positives control?

Step 1: Nest the dataset and split it in positives and samples

To streamline the process and make it easier to execute it similarly but independently on each Miseq run, we nest the dataset by run. So Step1 is create a nested table so we can run this analysis on each run independently.


ASV.table %>% 
  group_by(Miseq_run, source) %>% 
  nest() %>% 
  spread(source, data) -> ASV.nested 

That wasn’t too complicated. Let’s start a summary function that keeps track of our cleaning process

Step 2: Model the composition of the positive controls of each run

We create a vector of the composition of each positive control and substract it from the environmental samples from their runs



ASV.nested %>% 
  mutate (contam.tibble = map(Positives, 
                              function(.x){
                                .x %>%                    # Take the tibble from each run
                                  group_by(sample) %>%    
                                  mutate (TotalReadsperSample = sum(nReads)) %>%          # How many reads per sample 
                                  mutate (proportion = nReads/TotalReadsperSample) %>%    # What proportion does each Hash represent
                                  group_by (Hash) %>%
                                  summarise (vector_contamination = max (proportion))     # What is the maximum proportion that Hash ever gets
                                }) ) -> ASV.nested

ASV.nested %>% 
  unnest(contam.tibble) %>% 
  group_by(Miseq_run) %>% 
  summarise(nhash = n_distinct(Hash))# Check how it looks like
NA
NA
NA

Step 3: Substract the composition of the positive controls from the environment samples

The idea behind this procedure is that we know, for each run, how many reads from each Hash appeared in teh positive controls. These come from 2 processes: sequences we know should appear in the positive controls, and sequences that have jumped from the environment to the positive controls. With this procedure, we substract from every environmental sample the proportion of reads that jumped from elsewhere.

ASV.nested %>% 
  mutate(cleaned.tibble = map2(Samples, contam.tibble, function(.x,.y){  # We need two columns: the sample data and the contamination tibble
    .x %>%      # Get the samples tibble
      
      group_by (sample) %>%
      
      mutate (TotalReadsperSample = sum (nReads)) %>% # Calculate the number of reads per sample
      
      left_join(.y, by = "Hash")  %>%                  # Add, for each Hash shared with the positives, the value of the contamination vector
      # 
       mutate (Updated_nReads = case_when (!is.na(vector_contamination)    ~ (as.numeric(nReads) - (ceiling(vector_contamination*TotalReadsperSample))),
                                          
                                          TRUE                         ~ as.numeric(nReads)))  %>% # If the hash is present in the positives, substract the value of the vector times the total sample depth, otherwise, leave it as it is
      filter (Updated_nReads > 0) %>%       # Kepp only values > 0
      ungroup() %>%
      dplyr::select (sample, Hash, nReads = Updated_nReads)  # Leave no trace
      
    
  })) -> ASV.nested

ASV.nested %>% 
  unnest(cleaned.tibble) #Check how they look
NA
NA

Add this step to the summary table we were creating

ASV.nested %>% 
  transmute(Miseq_run, Summary.1 = map(cleaned.tibble, ~ how.many(ASVtable = .,round = "1.Jump"))) %>% 
  left_join(ASV.summary) %>% 
  mutate(Summary   = map2(Summary, Summary.1, bind_rows)) %>%
  dplyr::select(-Summary.1) -> ASV.summary 
Joining, by = "Miseq_run"
# Check how does it look

ASV.summary %>% 
  unnest(Summary)
NA

Cleaning Process 2: Discarding PCR replicates with low number of reads

We will fit the number of reads assigned to each sample to a normal distribution and discard those samples with a probability of 95% of not fitting in that distribution. The output would be a dataset with less samples and potentially less number of unique Hashes.


ASV.nested %>% 
  unnest(cleaned.tibble) %>% 
  group_by(sample) %>%
  summarise(tot = sum(nReads)) -> all.reps

# Visualize

all.reps %>%  
  pull(tot) -> reads.per.sample

names(reads.per.sample) <- all.reps %>% pull(sample)  

normparams.reads <- MASS::fitdistr(reads.per.sample, "normal")$estimate   # Fit the number of reads per sample to a normal distribution



all.reps %>%  
  mutate(prob = pnorm(tot, normparams.reads[1], normparams.reads[2])) -> all.reps  # Probability of each depth to come from teh same distribution



outliers <- 
  all.reps %>% 
  filter(prob < 0.05 & tot < normparams.reads[1])    # Which sample are below 0.1

outliers %>% 
  arrange(tot) # Some have as few as 4k
  
outliers %>% 
  arrange(desc(tot))  # Max number of reads of a sample removed is 20k


ASV.nested %>% 
  mutate(Step.1.low.reads = map (cleaned.tibble, ~ filter(.,!sample %in% outliers$sample) %>% ungroup)) -> ASV.nested # Remove outliers

ASV.nested %>% 
  transmute(Miseq_run, Summary.1 = map(Step.1.low.reads, ~ how.many(ASVtable = .,round = "2.Low.nReads"))) %>% 
  left_join(ASV.summary) %>% 
  mutate(Summary   = map2(Summary, Summary.1, bind_rows)) %>%
  dplyr::select(-Summary.1) -> ASV.summary 
Joining, by = "Miseq_run"

Cleaning Process 3: Full clearance from Positive control influence

Removing the Hashes that belong to the positive controls. First, for each Hash that appeared in the positive controls, determine whether a sequence is a true positive or a true environment. For each Hash, we will calculate, maximum, mean and total number of reads in both positive and samples, and then we will use the following decission tree:

  • If all three statistics are higher in one of the groups, we will label it either of Environmental or Positive control influence.

  • If there are conflicting results, we will use the Hashes. to see if they belong to either the maximum abundance of a Hash is in a positive, then it is a positive, otherwise is a real sequence from the environment.

Now, for each Hash in each set of positives controls, calculate the proportion of reads that were missasigned - they appeared somewhere they were not expected. We will divide that process in two: first . A second step would be to create a column named proportion switched, which states the proportion of reads from one Hash that jumped from the environment to a positive control or viceversa. The idea is that any presence below a threshold can be arguably belong to tag jumping.



ASV.table %>%                                         # Taking the whole dataset
  filter (Hash %in% list.of.hashes.in.positive) %>%   # keep only those hashes that have shown at least once in a positive control
  group_by(sample) %>% 
  mutate(tot.reads = sum(nReads)) %>% 
  group_by(Hash,sample) %>% 
  mutate(prop = nReads/tot.reads) %>% 
  group_by(Hash, source) %>% 
  summarise (max.  = max(prop),
             mean. = mean(prop),
             tot.  = sum(nReads)) %>% 
  gather(contains("."), value = "number", key = "Stat") %>%
  spread(key = "source", value = "number", fill = 0) %>% 
  group_by(Hash, Stat) %>%
  mutate(origin = case_when(Positives > Samples ~ "Positive.control",
                            TRUE                ~ "Environment")) %>% 
  group_by (Hash) %>%
  mutate(tot = n_distinct(origin)) -> Hash.fate.step2

# Do all three measurements agree

 Hash.fate.step2 %>% 
   filter(tot != 1 )

# No - remove only those for which the three measurements agree
 
 
Hash.fate.step2 %>% 
  filter(tot == 1) %>% 
  group_by(Hash) %>% 
  summarise(origin = unique(origin)) %>% 
  filter(origin == "Positive.control") -> Hashes.to.remove.step2

ASV.table %>% 
  group_by(source, Hash) %>% 
  summarise(ocurrences =n()) %>% 
  spread(key = source, value = ocurrences, fill = 0) %>% 
  #left_join(Hashes.to.remove.step2) %>% 
  #mutate(origin = case_when(is.na(origin) ~ "Kept",
   #                         TRUE          ~ "Discarded")) %>% 
  mutate(second.origin = case_when(Positives >= Samples ~ "Discarded",
                                   TRUE                 ~ "Kept")) %>% 
  filter(second.origin == "Discarded") %>% 
  full_join(Hashes.to.remove.step2) -> Hashes.to.remove.step2
Joining, by = "Hash"

IN order to train DADA2 to better distinguish when positive control sequences have arrived in the environment, we will keep the sequences in a csv file

rr

Hashes.to.remove.step2 %>% left_join(Hash.key) %>% select(Hash, Sequence) %>% write_csv(.to.remove.csv)

Remove the positive control hashes from the composition of the ASVs

Cleaning Process 4: Occupancy modelling

What is the probabilty of a true positive presence of a Hash in a Miseq Run. We will use eDNA occupancy modeling to asses whether a hash is a rare variant that spilled out or a true presence.

The process requires to load extra packages, create some model file, and group the hashes by Run, and biological replicate, summarising the data in a presence absence format.

The occupancy model itself was performed in the Rmarkdown file Rjags.tunning.Rmd, so here we will upload the csv file that contains all probability of occurences of all hashes per site. Each Hash-Site combination produces a matrix of presence abascences that feeds the model - for some cases it is a 30x3 matrix, for others it is a 39x3. We summarised the number of occurences in each case and run models for each unique case (to save computing time). Each unique model was run 10 times to filter out cases in which the model converge into a local maxima.

So we will import the object Occ.fate.csv and reduce the dataset to those Hashes with an occ > 0.8


occ.results <- read_csv("Occ.fate.csv")
Parsed with column specification:
cols(
  Hash = col_character(),
  model = col_double()
)
occ.results %>% 
  ggplot(aes(x = model)) +
  geom_histogram()


occ.results %>% 
  left_join(ASV.nested %>% 
              unnest(Step2.tibble) %>% 
              group_by(Hash) %>% 
              summarise (tot = sum(nReads))) %>% 
  ggplot(aes(x = cut_interval(model, n = 20))) +
  geom_col(aes(y = tot), fill = "red") 
Joining, by = "Hash"

So we will throw away most of the Hashes, but will keep most of the reads - we are getting into something here

 occ.results %>% 
  filter(model > 0.8) %>% 
  pull (Hash) -> to.keep

ASV.nested %>% 
  mutate(Step3.tibble = map (Step2.tibble, ~ filter(.,Hash %in% to.keep))) -> ASV.nested

ASV.nested %>% 
  transmute(Miseq_run, Summary.1 = map(Step3.tibble, ~ how.many(ASVtable = .,round = "4.Occupancy"))) %>% 
  left_join(ASV.summary) %>% 
  mutate(Summary   = map2(Summary, Summary.1, bind_rows)) %>%
  dplyr::select(-Summary.1) -> ASV.summary 
Joining, by = "Miseq_run"

Cleaning Process 5: Dissimilarity between PCR replicates

So, a second way of cleaning the dataset is to remove samples for which the dissimilarity between PCR replicates exceeds the normal distribution of dissimilarities. Sometimes the preparation of a PCR replicate goes wrong for a number of reasons - that leads to a particular PCR replicate to be substantially different to the other 2. In that case, we will remove the PCR replicate that has higher dissimilarity with the other two.

The process starts by adding the biological information to the ASV table, then diving the dataset by their biological replicate. This will also remove any sample that is not included in the metadata, eg coming from a different project.


ASV.nested %>% 
  unnest(Step3.tibble) %>%
  separate(sample, into = c(1:4,"rep", "run"), sep = "_", remove = F) %>%
  unite(`1`,`2`,`3`,`4`, run, col = "original_sample") -> cleaned.tibble

rr # do all samples have a name cleaned.tibble %>% filter (sample == \) # YES

do all of them have an original sample

cleaned.tibble %>% filter(original_sample == \) # YES

do all of them have a Hash

cleaned.tibble %>% filter(is.na(Hash)) # YES

How many samples, how many Hashes

cleaned.tibble %>% summarise(n_distinct(sample), # 325 n_distinct(Hash)) # 3166

Let’s check the levels of replication

cleaned.tibble %>% group_by(original_sample) %>% summarise(nrep = n_distinct(sample)) %>% #filter (nrep == 2) # 13 filter (nrep == 1) # 4

Ok, so there are 4 for which we only have 1 PCR replicate and 13 samples for which we only have 2 PCR replicates1. We will get rid of those with only 1, as we can’t estimate the PCR bias there.

discard.1 <- cleaned.tibble %>% 
  group_by(original_sample) %>% 
  mutate(nrep = n_distinct(sample)) %>% 
  #filter (nrep == 2) # 25
  filter (nrep == 1) %>% 
  distinct(sample) %>% pull(sample)

cleaned.tibble %>% 
  filter(!sample %in% discard.1) -> cleaned.tibble

Anyway, let’s have a visual representation of the dissimilarities between PCR replicates, biological replicates and everything else.

cleaned.tibble %>%
  group_by (sample) %>%
  mutate (Tot = sum(nReads),
          Row.sums = nReads / Tot) %>% 
  group_by (Hash) %>%
  mutate (Colmax = max (Row.sums),
          Normalized.reads = Row.sums / Colmax) -> cleaned.tibble
tibble_to_matrix <- function (tb) {
  
  tb %>% 
    group_by(sample, Hash) %>% 
    summarise(nReads = sum(Normalized.reads)) %>% 
    spread ( key = "Hash", value = "nReads", fill = 0) -> matrix_1
    samples <- pull (matrix_1, sample)
    matrix_1 %>% 
      ungroup() %>% 
    dplyr::select ( - sample) -> matrix_1
    data.matrix(matrix_1) -> matrix_1
    dimnames(matrix_1)[[1]] <- samples
    vegdist(matrix_1) -> matrix_1
}

tibble_to_matrix (cleaned.tibble) -> all.distances.full

#names(all.distances.full)


summary(is.na(names(all.distances.full)))
   Mode   FALSE 
logical     320 

Let’s make the pairwaise distances a long table

as_tibble(as.matrix(all.distances.full) , rownames = "Sample1") %>% 
  gather(-Sample1, key = "Sample2", value = "value") -> all.distances.melted

summary(is.na(all.distances.melted$value))
   Mode   FALSE 
logical  102400 
# Now, create a three variables for all distances, they could be PCR replicates, BIOL replicates, or from the same site

all.distances.melted %>%
  separate (Sample1, into = c("Site1", "A", "B", "Month1", "rep", "round"), sep = "_", remove = FALSE) %>%
  unite (Site1,Month1, col = "Day.site1", remove = FALSE) %>%
  unite (Site1, Month1, A, B, round, col = "Bottle1", remove = F) %>% 
  separate (Sample2, into = c("Site2", "A2", "B2", "Month2", "rep2", "round2"), sep = "_", remove = FALSE) %>%
  unite (Site2, Month2, col = "Day.site2", remove = FALSE) %>%
  unite (Site2, Month2, A2, B2, round2, col = "Bottle2", remove = F) %>% 
  mutate ( Distance.type = case_when( Bottle1 == Bottle2 ~ "PCR.replicates",
                                      Day.site1 == Day.site2 ~ "Biol.replicates",
                                      Site1 == Site2 ~ "Same Site",
                                      TRUE ~ "Different Site"
                                     )) %>%
  dplyr::select(Sample1 ,Sample2  , value , Distance.type) %>%
  filter (Sample1 != Sample2) -> all.distances.to.plot

# Checking all went well

sapply(all.distances.to.plot, function(x) summary(is.na(x)))
      Sample1   Sample2   value     Distance.type
Mode  "logical" "logical" "logical" "logical"    
FALSE "102080"  "102080"  "102080"  "102080"     
all.distances.to.plot$Distance.type <- all.distances.to.plot$Distance.type  %>% fct_relevel( "PCR.replicates", "Biol.replicates", "Same Site")

  ggplot (all.distances.to.plot , aes (fill = Distance.type, x = value)) +
  geom_histogram (position = "dodge", stat = 'density', alpha = 0.9) +
 # facet_wrap( ~ Distance.type) +
  labs (x = "Pairwise dissimilarity", y = "density" ,
        Distance.type = "Distance")
Ignoring unknown parameters: binwidth, bins, pad

NA
# Instead of chosing based on the pw distances, we can do a similar thing using the distance to centroid

# Find out which samples have only two pcr replicates
cleaned.tibble %>% dplyr::select(-Miseq_run) %>% group_by(original_sample) %>% nest() -> nested.cleaning

nested.cleaning %>% 
  mutate(matrix = map(data, tibble_to_matrix)) -> nested.cleaning
nested.cleaning %>% mutate(ncomparisons = map(matrix, length)) -> nested.cleaning
 
  
dist_to_centroid <- function (x,y) {
  biol <- rep(y, length(x))
  
  if (length(biol) == 1) {
    output = rep(x[1]/2,2)
    names(output) <- attr(x, "Labels")
  }else{ 
    
  dispersion <- betadisper(x, group = biol)
  output = dispersion$distances
  }
  output
    }


nested.cleaning <- nested.cleaning %>% mutate (distances = map2(matrix, original_sample, dist_to_centroid))

unlist (nested.cleaning$distances) -> all_distances
#normparams <- fitdistr(all_pairwise_distances, "normal")$estimate
normparams <- MASS::fitdistr(all_distances, "normal")$estimate
#  probs <- pnorm(all_pairwise_distances, normparams[1], normparams[2])
probs <- pnorm(all_distances, normparams[1], normparams[2])
outliers <- which(probs>0.95)

discard <-names (all_distances[outliers])

all_distances
 CI_Ac_6_Jul_A_r1  CI_Ac_6_Jul_B_r1  CI_Ac_6_Jul_C_r1 PG_Al_10_Jul_A_r1 PG_Al_10_Jul_B_r1 PG_Al_10_Jul_C_r1 PG_Ac_10_Jul_A_r1 PG_Ac_10_Jul_B_r1 PG_Ac_10_Jul_C_r1  CI_Eg_0_Jul_A_r1 
       0.24200187        0.34174402        0.32306106        0.34569852        0.39375650        0.21568054        0.28502422        0.39321312        0.30761436        0.22918036 
 CI_Eg_0_Jul_B_r1  CI_Eg_0_Jul_C_r1  PG_Al_6_Jul_A_r1  PG_Al_6_Jul_B_r1  PG_Al_6_Jul_C_r1  PG_Eg_0_Jul_A_r1  PG_Eg_0_Jul_B_r1  PG_Eg_0_Jul_C_r1  PG_Al_1_Jul_A_r1  PG_Al_1_Jul_B_r1 
       0.25999527        0.31929866        0.33851352        0.30221775        0.26451030        0.35466640        0.39655652        0.29528919        0.31310653        0.32123476 
 PG_Al_1_Jul_C_r1 PG_Ac_15_Jul_A_r1 PG_Ac_15_Jul_B_r1 PG_Ac_15_Jul_C_r1  PG_Ac_6_Jul_A_r1  PG_Ac_6_Jul_B_r1  PG_Ac_6_Jul_C_r1 PG_Ba_50_Jul_A_r1 PG_Ba_50_Jul_B_r1 PG_Ba_50_Jul_C_r1 
       0.25710159        0.31107969        0.14741295        0.35248469        0.19075655        0.18703610        0.24260378        0.32953600        0.47296674        0.35552258 
 PG_Al_3_Jul_A_r1  PG_Al_3_Jul_B_r1  PG_Al_3_Jul_C_r1 PG_Al_15_Jul_A_r1 PG_Al_15_Jul_B_r1 PG_Al_15_Jul_C_r1  PG_Ac_3_Jul_A_r1  PG_Ac_3_Jul_B_r1  PG_Ac_3_Jul_C_r1  PG_Ac_1_Jul_A_r1 
       0.43226523        0.57354538        0.47514697        0.33374010        0.26613421        0.32387645        0.55634241        0.46461681        0.49958454        0.19241471 
 PG_Ac_1_Jul_B_r1  PG_Ac_1_Jul_C_r1 CI_Ba_50_Jul_A_r1 CI_Ba_50_Jul_B_r1 CI_Ba_50_Jul_C_r1  CI_Al_6_Jul_A_r1  CI_Al_6_Jul_B_r1  CI_Al_6_Jul_C_r1  CI_Al_3_Jul_A_r1  CI_Al_3_Jul_B_r1 
       0.19449552        0.24643142        0.31675244        0.22469647        0.23088452        0.26109615        0.26858849        0.24895741        0.11841821        0.24911663 
 CI_Al_3_Jul_C_r1  CI_Al_1_Jul_A_r1  CI_Al_1_Jul_B_r1  CI_Al_1_Jul_C_r1 CI_Ac_10_Jul_A_r1 CI_Ac_10_Jul_B_r1 CI_Ac_10_Jul_C_r1 CI_Al_10_Jul_A_r1 CI_Al_10_Jul_B_r1 CI_Al_10_Jul_C_r1 
       0.34861559        0.24110032        0.27353656        0.24121474        0.25870697        0.35651093        0.35253060        0.25418451        0.29351107        0.31393867 
CI_Ac_15_Jul_A_r1 CI_Ac_15_Jul_B_r1 CI_Ac_15_Jul_C_r1  CI_Ac_3_Jul_A_r1  CI_Ac_3_Jul_B_r1  CI_Ac_3_Jul_C_r1  CI_Ac_1_Jul_A_r1  CI_Ac_1_Jul_B_r1  CI_Ac_1_Jul_C_r1  NR_Eg_0_Jul_A_r2 
       0.30993978        0.34355805        0.30402237        0.32284570        0.30838100        0.31709095        0.35613976        0.33536263        0.36095813        0.29007245 
 NR_Eg_0_Jul_B_r2  NR_Eg_0_Jul_C_r2  NR_Eg_0_Aug_A_r2  NR_Eg_0_Aug_B_r2  NR_Eg_0_Aug_C_r2 CI_Ba_50_May_A_r2 CI_Ba_50_May_B_r2 CI_Ba_50_May_C_r2 PG_Ba_50_Jul_A_r2 PG_Ba_50_Jul_B_r2 
       0.22035554        0.42125162        0.53303093        0.34041943        0.29168929        0.33546256        0.34932460        0.33702868        0.30398593        0.44900583 
PG_Ba_50_Jul_C_r2 NR_Ba_50_Jul_A_r2 NR_Ba_50_Jul_B_r2 NR_Ba_50_Jul_C_r2 SK_Ba_50_Jul_A_r2 SK_Ba_50_Jul_B_r2 SK_Ba_50_Jul_C_r2 CI_Al_15_Jul_A_r2 CI_Al_15_Jul_B_r2 CI_Al_15_Jul_C_r2 
       0.35210709        0.31295169        0.31807135        0.35306339        0.18418890        0.16238477        0.27181311        0.25776458        0.32829312        0.23044470 
 PG_Eg_0_May_A_r2  PG_Eg_0_May_B_r2  PG_Eg_0_May_C_r2 NR_Ba_50_May_A_r2 NR_Ba_50_May_B_r2 NR_Ba_50_May_C_r2  NR_Eg_0_May_A_r2  NR_Eg_0_May_B_r2  NR_Eg_0_May_C_r2 NR_Ba_50_Aug_A_r2 
       0.22697359        0.27968790        0.34636294        0.26005736        0.30216057        0.25317823        0.32864704        0.31403972        0.35679845        0.33774239 
NR_Ba_50_Aug_B_r2 NR_Ba_50_Aug_C_r2 PG_Ba_50_May_A_r2 PG_Ba_50_May_B_r2 PG_Ba_50_May_C_r2  CI_Eg_0_Aug_A_r2  CI_Eg_0_Aug_B_r2  CI_Eg_0_Aug_C_r2  CI_Eg_0_May_A_r2  CI_Eg_0_May_B_r2 
       0.34260413        0.31942453        0.26507578        0.21317699        0.25019818        0.26537513        0.34614518        0.36713967        0.21900841        0.28320224 
 CI_Eg_0_May_C_r2  CI_Eg_0_Jul_A_r2  CI_Eg_0_Jul_B_r2  CI_Eg_0_Jul_C_r2  PG_Eg_0_Aug_A_r2  PG_Eg_0_Aug_B_r2  PG_Eg_0_Aug_C_r2  SK_Eg_0_Jul_A_r2  SK_Eg_0_Jul_B_r2  SK_Eg_0_Jul_C_r2 
       0.27460340        0.23672489        0.22090377        0.28397489        0.32394486        0.28501121        0.29141161        0.28678923        0.26117762        0.28734971 
CI_Ba_50_Aug_A_r2 CI_Ba_50_Aug_B_r2 CI_Ba_50_Aug_C_r2 SK_Ba_50_May_A_r2 SK_Ba_50_May_B_r2 SK_Ba_50_May_C_r2  SK_Eg_0_May_A_r2  SK_Eg_0_May_B_r2  SK_Eg_0_May_C_r2 PG_Ba_50_Aug_A_r2 
       0.29876689        0.33841614        0.33589507        0.27985906        0.33486241        0.32205972        0.36629655        0.40524124        0.36280857        0.27767793 
PG_Ba_50_Aug_B_r2 PG_Ba_50_Aug_C_r2  SK_Eg_0_Aug_A_r2  SK_Eg_0_Aug_B_r2  SK_Eg_0_Aug_C_r2 SK_Ba_50_Aug_A_r2 SK_Ba_50_Aug_B_r2 SK_Ba_50_Aug_C_r2 PG_Al_10_Aug_A_r3 PG_Al_10_Aug_B_r3 
       0.19959278        0.34629456        0.09868401        0.30230470        0.34412819        0.22875852        0.35223723        0.34129988        0.45420446        0.46989538 
PG_Al_10_Aug_C_r3  PG_Al_1_Aug_A_r3  PG_Al_1_Aug_B_r3  PG_Al_1_Aug_C_r3  PG_Al_6_Aug_A_r3  PG_Al_6_Aug_B_r3  PG_Al_6_Aug_C_r3 PG_Al_15_Aug_A_r3 PG_Al_15_Aug_B_r3 PG_Al_15_Aug_C_r3 
       0.50160669        0.38051839        0.44913027        0.53447100        0.59542974        0.52314628        0.52313007        0.49347501        0.29470489        0.40220504 
 PG_Al_3_Aug_A_r3  PG_Al_3_Aug_B_r3  PG_Al_3_Aug_C_r3 CI_Al_15_Aug_A_r3 CI_Al_15_Aug_B_r3 CI_Al_15_Aug_C_r3  CI_Al_3_May_A_r3  CI_Al_3_May_B_r3  CI_Al_3_May_C_r3  CI_Al_1_May_A_r3 
       0.39343493        0.37371046        0.53464146        0.35819558        0.70786448        0.37774145        0.41749960        0.46179586        0.47310930        0.38503137 
 CI_Al_1_May_B_r3  CI_Al_1_May_C_r3  CI_Al_6_Aug_A_r3  CI_Al_6_Aug_B_r3  CI_Al_6_Aug_C_r3 CI_Al_10_May_A_r3 CI_Al_10_May_B_r3 CI_Al_10_May_C_r3  PG_Al_1_May_A_r3  PG_Al_1_May_B_r3 
       0.30890161        0.34060883        0.36106073        0.60419559        0.36449527        0.33875876        0.38102592        0.39789148        0.36481755        0.41894267 
 PG_Al_1_May_C_r3  CI_Al_6_May_A_r3  CI_Al_6_May_B_r3  CI_Al_6_May_C_r3  CI_Al_1_Aug_A_r3  CI_Al_1_Aug_B_r3  CI_Al_1_Aug_C_r3 CI_Al_10_Aug_A_r3 CI_Al_10_Aug_B_r3 CI_Al_10_Aug_C_r3 
       0.40066632        0.23444420        0.14529500        0.20385169        0.30268467        0.37456491        0.35522730        0.34475791        0.67690891        0.34261752 
 PG_Al_3_May_A_r3  PG_Al_3_May_B_r3  PG_Al_3_May_C_r3 PG_Al_15_May_A_r3 PG_Al_15_May_B_r3 PG_Al_15_May_C_r3  PG_Al_6_May_A_r3  PG_Al_6_May_B_r3  PG_Al_6_May_C_r3  CI_Al_3_Aug_A_r3 
       0.43592808        0.44160259        0.43533662        0.31669588        0.33368310        0.44518536        0.35996621        0.33542982        0.34252419        0.29998097 
 CI_Al_3_Aug_B_r3  CI_Al_3_Aug_C_r3 PG_Al_10_May_A_r3 PG_Al_10_May_B_r3 PG_Al_10_May_C_r3 CI_Al_15_May_A_r3 CI_Al_15_May_B_r3 CI_Al_15_May_C_r3  SK_Al_3_Aug_A_r4  SK_Al_3_Aug_B_r4 
       0.49969776        0.21262439        0.35959016        0.35008060        0.25706306        0.53227846        0.45169104        0.52530436        0.25851686        0.25851686 
 SK_Al_6_Aug_A_r4  SK_Al_6_Aug_C_r4 SK_Al_10_Aug_A_r4 SK_Al_10_Aug_B_r4 SK_Al_10_Aug_C_r4  SK_Al_6_Jul_A_r4  SK_Al_6_Jul_C_r4 SK_Al_15_Aug_A_r4 SK_Al_15_Aug_B_r4 NR_Ac_15_Aug_A_r4 
       0.18860066        0.18860066        0.56642189        0.41764497        0.48352706        0.14838293        0.14838293        0.41406761        0.41406761        0.25183525 
NR_Ac_15_Aug_C_r4 SK_Al_15_Jul_A_r4 SK_Al_15_Jul_C_r4  NR_Ac_3_Aug_A_r4  NR_Ac_3_Aug_B_r4  NR_Ac_3_Aug_C_r4 NR_Ac_10_Aug_A_r4 NR_Ac_10_Aug_B_r4 NR_Ac_10_Aug_C_r4 SK_Al_10_Jul_A_r4 
       0.25183525        0.11404550        0.11404550        0.44317436        0.43689397        0.65579365        0.48534467        0.66464032        0.48533689        0.25988738 
SK_Al_10_Jul_C_r4  NR_Ac_1_Aug_B_r4  NR_Ac_1_Aug_C_r4  NR_Ac_6_Jul_A_r4  NR_Ac_6_Jul_B_r4  NR_Ac_6_Jul_C_r4  NR_Ac_1_Jul_A_r4  NR_Ac_1_Jul_B_r4 NR_Ac_10_Jul_B_r4 NR_Ac_10_Jul_C_r4 
       0.25988738        0.37937801        0.37937801        0.46065944        0.54650343        0.50450129        0.31422890        0.31422890        0.33781188        0.33781188 
 NR_Ac_3_Jul_A_r4  NR_Ac_3_Jul_B_r4  NR_Ac_3_Jul_C_r4  SK_Al_1_Aug_A_r5  SK_Al_1_Aug_B_r5  SK_Al_1_Aug_C_r5  SK_Al_1_Jul_A_r5  SK_Al_1_Jul_B_r5  SK_Al_1_Jul_C_r5  SK_Al_3_Aug_B_r5 
       0.38081279        0.36151068        0.39520191        0.39544437        0.37272544        0.44879033        0.37597606        0.36446334        0.28335298        0.49325825 
 SK_Al_3_Aug_C_r5  SK_Al_3_Jul_A_r5  SK_Al_3_Jul_B_r5  SK_Al_3_Jul_C_r5  SK_Al_6_Aug_A_r5  SK_Al_6_Aug_B_r5  SK_Al_6_Aug_C_r5  SK_Al_6_Jul_A_r5  SK_Al_6_Jul_B_r5  SK_Al_6_Jul_C_r5 
       0.49325825        0.27161327        0.34808484        0.30041275        0.35028155        0.42081913        0.30624596        0.37380795        0.30137539        0.39044536 
SK_Al_10_Aug_A_r5 SK_Al_10_Aug_B_r5 SK_Al_10_Aug_C_r5 SK_Al_10_Jul_A_r5 SK_Al_10_Jul_B_r5 SK_Al_10_Jul_C_r5 SK_Al_15_Aug_B_r5 SK_Al_15_Aug_C_r5 SK_Al_15_Jul_A_r5 SK_Al_15_Jul_B_r5 
       0.42392161        0.48554773        0.41325803        0.35084553        0.37366133        0.45632517        0.42700613        0.42700613        0.28415899        0.31076536 
SK_Al_15_Jul_C_r5  WB_Al_1_Aug_B_r5  WB_Al_1_Aug_C_r5  WB_Al_1_Jul_A_r5  WB_Al_1_Jul_B_r5  WB_Al_1_Jul_C_r5  WB_Al_1_May_A_r5  WB_Al_1_May_B_r5  WB_Al_1_May_C_r5  WB_Al_3_Aug_A_r5 
       0.27045781        0.44991276        0.44991276        0.33009754        0.38438516        0.41764292        0.21472000        0.28153885        0.22984300        0.41662468 
 WB_Al_3_Aug_B_r5  WB_Al_3_Aug_C_r5  WB_Al_3_Jul_A_r5  WB_Al_3_Jul_B_r5  WB_Al_3_Jul_C_r5  WB_Al_3_May_A_r5  WB_Al_3_May_B_r5  WB_Al_3_May_C_r5  WB_Al_6_Aug_A_r5  WB_Al_6_Aug_B_r5 
       0.49120032        0.42663556        0.35145371        0.36377511        0.30185479        0.20106604        0.28675680        0.22790393        0.21670393        0.49749284 
 WB_Al_6_Aug_C_r5  WB_Al_6_Jul_A_r5  WB_Al_6_Jul_B_r5  WB_Al_6_Jul_C_r5  WB_Al_6_May_A_r5  WB_Al_6_May_B_r5  WB_Al_6_May_C_r5 WB_Al_10_Aug_A_r5 WB_Al_10_Aug_B_r5 WB_Al_10_Aug_C_r5 
       0.23327256        0.28130088        0.29041386        0.21110947        0.27810656        0.30600134        0.28343466        0.39905808        0.43383459        0.43522620 
WB_Al_10_Jul_A_r5 WB_Al_10_Jul_B_r5 WB_Al_10_Jul_C_r5 WB_Al_10_May_A_r5 WB_Al_10_May_B_r5 WB_Al_10_May_C_r5 WB_Al_15_Aug_A_r5 WB_Al_15_Aug_B_r5 WB_Al_15_Aug_C_r5 WB_Al_15_Jul_A_r5 
       0.32232232        0.32670417        0.35978460        0.39271670        0.24168786        0.25301545        0.44984319        0.49520462        0.47971103        0.26600345 
WB_Al_15_Jul_B_r5 WB_Al_15_Jul_C_r5 WB_Al_15_May_A_r5 WB_Al_15_May_B_r5 WB_Al_15_May_C_r5 WB_Ba_50_Aug_A_r5 WB_Ba_50_Aug_B_r5 WB_Ba_50_Aug_C_r5 WB_Ba_50_Jul_A_r5 WB_Ba_50_Jul_B_r5 
       0.29920657        0.18485765        0.20855562        0.31827490        0.22251747        0.24309575        0.28055177        0.25394471        0.19455368        0.28138514 
WB_Ba_50_Jul_C_r5 WB_Ba_50_May_A_r5 WB_Ba_50_May_B_r5 WB_Ba_50_May_C_r5  WB_Eg_0_Jul_A_r5  WB_Eg_0_Jul_B_r5  WB_Eg_0_Jul_C_r5  WB_Eg_0_May_A_r5  WB_Eg_0_May_B_r5  WB_Eg_0_May_C_r5 
       0.14919037        0.20415548        0.28372189        0.23176714        0.22378662        0.34875438        0.23739855        0.23747241        0.19179601        0.21029102 
to_write_discarded <- tibble(sample = discard, value = all_distances[outliers])

to_write_discarded <- to_write_discarded %>% bind_rows(tibble(sample = discard.1,
                                                              distance_to_centroid = NA))

Finally, let’s remove these samples from the dataset

ASV.nested %>% 
  mutate(Step4.tibble = map (Step3.tibble,  ~ filter(.,! sample %in% to_write_discarded$sample))) -> ASV.nested

ASV.nested %>% 
  transmute(Miseq_run, Summary.1 = map(Step4.tibble, ~ how.many(ASVtable = .,round = "5.PCR.dissimilarity"))) %>% 
  left_join(ASV.summary) %>% 
  mutate(Summary   = map2(Summary, Summary.1, bind_rows)) %>%
  dplyr::select(-Summary.1) -> ASV.summary 
Joining, by = "Miseq_run"

Exporting the output

We will export the final cleaned table with four columns (Miseq_run, sample, Hash, nReads)

rr

ASV.nested %>% unnest(Step4.tibble) %>% mutate(nReads = as.integer(nReads)) %>% write_csv(_data/ASV_table_all_together.csv)

ASV.nested %>% unnest(Step4.tibble) %>% distinct(Hash) %>% left_join(Hash.key) %>% write_csv(_data/Hash_Key_all_together.csv)

library(tidyverse) library(seqinr)

input <- read_csv(_data/Hash_Key_all_together.csv) output <- _data/Hash_Key_all_together.fasta

write.fasta (sequences = as.list(input\(Sequence), names = as.list(input\)Hash), file.out = output)

saveRDS(ASV.nested, file = .ASVs.nested)

Checking the output

Let’s check out the success of our approach - use the taxonomy annotation to look for vertebrate sequences

23.558632/28.289321
[1] 0.8327747

ASV.summary %>% 
  unnest() %>% 
  filter (Miseq_run!= "4") %>% 
  group_by(Stage, Stat) %>%
  summarise(number = sum(number)) %>% 
  spread(key = Stat , value = number)
  nest ()

#where are the salmons

ASV.nested %>% 
  unnest(Step4.tibble) %>% #        
  left_join(Taxonomy) %>% 
  separate(sample, into = "biol", sep = "\\.", remove = F) %>% 
  group_by(Family, biol) %>% 
  summarise (tot = sum(nReads)) %>% 
  filter (Family == "Salmonidae") %>% 
  arrange(desc(tot))

# Prevalence plot as in decontam package

ASV.table %>% 
  group_by(source, Hash) %>% 
  summarise(ocurrences =n()) %>% 
  spread(key = source, value = ocurrences, fill = 0) %>% 
  left_join(Hashes.to.remove.step2) %>% 
  mutate(origin = case_when(is.na(origin) ~ "Kept",
                            TRUE          ~ "Discarded")) %>% 
  ggplot(aes(x = Positives, y = Samples, color = origin))+
  geom_point()

ASV.table %>% 
  group_by(source, Hash) %>% 
  summarise(ocurrences =n()) %>% 
  spread(key = source, value = ocurrences, fill = 0) %>% 
  #left_join(Hashes.to.remove.step2) %>% 
  #mutate(origin = case_when(is.na(origin) ~ "Kept",
   #                         TRUE          ~ "Discarded")) %>% 
  mutate(second.origin = case_when(Positives >= Samples ~ "Discarded",
                                   TRUE                 ~ "Kept")) %>% 
  filter(second.origin == "Discarded") %>% 
  full_join(Hashes.to.remove.step2) -> Hashes.to.remove.step2
Hashes.to.remove.step2 %>% 
filter (Hash == "acebcd5c491bb273f3e4d615cafad649")
LS0tCnRpdGxlOiAiRGVub2lzaW5nIGFuZCBkZWNvbnRhbWluYXRpbmcgdjIiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KQWZ0ZXIgcnVubmluZyB0aGUgZGVtdWx0aXBsZXhlcl9mb3JfZGFkYTIgKGh0dHA6Ly9naXRodWIuY29tL3JhbW9uZ2FsbGVnby9kZW11bHRpcGxleGVyX2Zvcl9kYWRhMiksIHdlIGhhdmUgdG8gZGVub2lzZSB0aGUgd2hvbGUgZGF0YXNldC4gV2Ugd2lsbCBkbyB0aGlzIGJ5IHVzaW5nIDQgZGlmZmVyZW50IHByb2Nlc3NlczoKCgogICogKipFc3RpbWF0aW9uIG9mICpUYWctanVtcGluZyogb3IgaW5kaWNlcyAqY3Jvc3MtdGFsayogKiouIFdlIHJ1biBtdWx0aXBsZSBzYW1wbGVzIG9uIGVhY2ggTWlTZXEgcnVuLiBUaGVzZSBhcmUgaWRlbnRpZmllZCBieSB0d28gc2V0cyBvZiBtb2xlY3VsYXIgYmFyY29kZXMuIFRoZXJlIGlzIHRoZSBwb3RlbnRpYWwgb2Ygc29tZSBzZXF1ZW5jZXMgdG8gYmUgYXNzaWduZWQgdG8gdGhlIHdyb25nIHNhbXBsZSwgd2hpY2ggaXMgYSBidW1tZXIuIFRvIGVzdGltYXRlIGhvdyBtYW55IHJlYWRzIGRpZCB0aGlzLCBvbiBlYWNoIE1pU2VxIHJ1biB3ZSBhZGRlZCBzb21lIHNhbXBsZXMgd2hvc2UgY29tcG9zaXRpb24gaXMga25vd24gYW5kIGV4dHJlbWVseSB1bmxpa2VseSB0byBiZSBwcmVzZW50IGluIHRoZSBlbnZpcm9tZW50YWwgc2FtcGxlcyBzdHVkaWVkLiBBUyBhIHJlc3VsdCBvZiB0aGlzICoqVGFnLWp1bXBpbmcqKiwgc29tZSBvZiB0aGUgcG9zaXRpdmUgY29udHJvbCBzZXF1ZW5jZXMgbWlnaHQgc2hvdyBpbiB0aGUgZW52aXJvbm1lbnRhbCBzYW1wbGVzIGFuZCB2aWNldmVyc2EuIEluIG91ciBjYXNlLCB0aGVzZSBwb3NpdGl2ZSBjb250cm9scyBhcmUgbWFkZSBvZiBlaXRoZXIgS2FuZ2Fyb28gb3IgT3N0cmljaCAoYW5kIEFsbGlnYXRvcikuIFRoZSBwcm9jZXNzIGNvbnNpc3RzIG9uLCBmb3IgZWFjaCBydW4sIHRvIG1vZGVsIHRoZSBjb21wb3NpdG9uIG9ic2VydmVkIG9uIHRoZSBwb3NpdGl2ZSBjb250cm9scyBhbmQgc3Vic3RyYWN0IGl0IGZyb20gdGhlIGVudmlyb25tZW50YWwgc2FtcGxlcyBmcm9tIHRoYXQgcnVuLiBUaGUgb3V0cHV0IHdpbGwgYmUgYSBkYXRhc2V0IHdpdGggdGhlIHNhbWUgbnVtYmVyIG9mIHNhbXBsZXMgYXMgYmVmb3JlLCBidXQgd2l0aCBmZXdlciByZWFkcyBvZiBjZXJ0YWluIHNlcXVlbmNlcyAoQVNWcykKICAKICAqICoqRGlzY2FyZGluZyBzYW1wbGVzIHdpdGggZXh0cmVtZWx5IGxvdyBudW1iZXIgb2YgcmVhZHMqKi4gU29tZXRpbWVzIHRoZSBudW1iZXIgb2YgcmVhZHMgc2VxdWVuY2VkIGZyb20gYSBwYXJ0aWN1bGFyIHJlcGxpY2F0ZSBhcmUgcmVhbGx5IGxvdywgYW5kIGhlbmNlIHRoZSByZWxhdGl2ZSBwcm9wb3J0aW9ucyBvZiBBU1ZzIHdvdWxkIGJlIHNrZXdlZC4gCiAgCiAgKiAqKkZ1bGwgY2xlYXJhbmNlIGZyb20gUG9zaXRpdmUgY29udHJvbCBpbmZsdWVuY2UqKi4gVEhpcyBwcm9jZXNzIGFsc28gdGFrZXMgYWR2YW50YWdlIG9mIHRoZSBrbm93biBjb21wb3NpdGlvbiBvZiB0aGUgcG9zaXRpdmUgY29udHJvbHMuIEVhY2ggQVNWIGZvdW5kIGluIHRoZSBwb3NpdGl2ZSBjb250cm9scyB3aXRoIGEgaGlnaGVyIGFidW5kYWNlIGluIHRoZW0gdGhhbiBpbiB0aGUgcmVzdCBvZiB0aGUgc2FtcGxlcyB3aWxsIGJlIGxhYmVsbGVkIGFzICAqKlBvc2l0aXZlKiogYW5kIHJlbW92ZWQgZnJvbSB0aGUgZW52aXJvbm1lbnRhbCBkYXRhc2V0LiBUaGUgb3V0cHV0IHdpbGwgYmUgYSBkYXRhc2V0IHdpdGggdGhlIHNhbWUgbnVtYmVyIG9mIHNhbXBsZXMgYXMgYmVmb3JlIGJ1dCB3aXRoIGZld2VyIEFTVnMuCiAgCiAgKiAqKk9jY3VwYW5jeSBtb2RlbGxpbmcqKiAuIElzIHRoZSBwcmVzZW5jZSBvZiBhIEFTViBhIHJlZmxlY3Rpb24gb2YgYSBiaW9sb2dpY2FsIHJlYWxpdHkgb3IgbGlrZWx5IGEgUENSIGFydGlmYWN0PyBUaGlzIG1heSBzZWVtIHRyaXZpYWwgaW4gZXh0cmVtZSBjYXNlcyAoYW4gQVNWIHRoYXQgb25seSBhcHBlYXJzIGluIG9uZSBQQ1IgcmVwbGljYXRlIGluIHRoZSB3aG9sZSBkYXRhc2V0KSBidXQgaG93IHRvIGRpc2NyaW1pbmF0ZSBiZXR3ZWVuIFBDUiBhcnRpZmFjdHMgZnJvbSByYXJlIGJ1dCByZWFsIG9yZ2FuaXNtcz8gV2UgdXNlIE9jY3VwYW5jeSBtb2RlbGxpbmcgdG8gZGV0ZXJtaW5lIGlmIHRoZSBwYXR0ZXJuIG9mIHByZXNlbmNlIG9mIGEgQVNWIGluIGEgZGF0YXNldCByZWZsZWN0cyB0aGF0LiBUaGUgb3V0cHV0IG9mIHRoaXMgcHJvY2VkdXJlIHdpbGwgYmUgYSBkYXRhc2V0d2l0aCB0aGUgc2FtZSBudW1iZXIgb2Ygc2FtcGxlcyBhcyBiZWZvcmUgYnV0IHdpdGggZmV3ZXIgQVNWcy4KICAKICAqICoqRGlzc2ltaWxhcml0eSBiZXR3ZWVuIFBDUiByZXBsaWNhdGVzKiouIFRoZSB3b3JrZmxvdyB0aGF0IGxlYWRzIHRvIHRoZSBzZXF1ZW5jaW5nIG9mIGEgcGFydGljdWxhciBzYW1wbGUgaXMgc3ViamVjdCB0byBtYW55IHN0b2NoYXRpYyBwcm9jZXNzZXMsIGFuZCBpdCBpcyBub3QgdW5saWtlbHkgdGhhdCB0aGUgY29tcG9zaXRpb24gcmV0cmlldmVkIGlzIHZlcnkgZGlmZmVyZW50IGZvciB0aGUgb3JpZ2luYWwgY29tbXVuaXR5LiBBIHdheSB0byBlbnN1cmUgdGhhdCB0aGlzIGRpZmZlcmVuY2UgaXMgbWluaW1hbCBpcyB0aHJvdWdoIHRoZSBzZXBhcmF0ZSBhbmFseXNpcyBvZiBlYWNoIFBDUiByZXBsaWNhdGUuIFdlIHVzZWQgdGhhdCBhcHByb2FjaCBhbmQgbW9kZWxlZCB0aGUgZGlzc2ltaWxhcml0eSBiZXR3ZWVuIGVhY2ggUENyIHJlcGxpY2F0ZSBhbmQgdGhlIGdyb3VwIGNlbnRyb2lkLiBUaGlzIHdheSBvZiBtb2RlbGluZyB0aGUgZGlzc2ltaWxhcml0eSBhbGxvd3MgdXMgdG8gZGlzY2FyZCB0aG9zZSBQQ1IgcmVwbGljYXRlIHRoYXQgd29uJ3QgZml0IHRoZSBub3JtYWwgZGlzdHJpYnV0aW9uIG9mIGRpc3NpbWlsYXJpdGllcy4gVGhlIG91dHB1dCBvZiB0aGlzIHByb2NlZHVyZSB3aWxsIGJlIGEgZGF0YXNldCB3aXRoIHRoZSBzYW1lIG51bWJlciBvZiAqKkhhc2hlcyoqIGFzIGJlZm9yZSBidXQgd2l0aCBmZXdlciAqKnNhbXBsZXMqKi4KICAKICAKQXMgd2l0aCBldmVyeXRoaW5nLCB3ZSB3aWxsIHN0YXJ0IHRoZSBwcm9jZXNzIGJ5IGxvYWRpbmcgdGhlIHJlcXVpcmVkIHBhY2thZ2VzIGFuZCBkYXRhc2V0cy4KCiMgTG9hZCB0aGUgZGF0YXNldCBhbmQgbWV0YWRhdGEKCgoKYGBge3IgbG9hZCBsaWJyYXJpZXMsIGluY2x1ZGU9RkFMU0V9CiBrbml0cjo6b3B0c19jaHVuayRzZXQod2FybmluZyA9IEZBTFNFKQoKIGxpYnJhcnkgKHRpZHl2ZXJzZSkKIGxpYnJhcnkgKHZlZ2FuKQogI2xpYnJhcnkgKE1BU1MpCiBsaWJyYXJ5IChwcm94eSkKbGlicmFyeShyZXNoYXBlMikKCmBgYAoKV2Ugd2lsbCBsb2FkIHRoZSBBU1YgdGFibGUgYW5kIHRoZSBtZXRhZGF0YSBmaWxlLiBUaGV5IGFyZSBpbiB0aGUgc2FtZSBmb2xkZXIgc28gd2UgdXNlIGBsaXN0LmZpbGVzYCB0byBhY2Nlc3MgdGhlbSBhbmQgYSBuZWF0IGNvbWJpbmF0aW9uIG9mIGBiaW5kLnJvd3NgIGFuZCBgbWFwKHJlYWRfY3N2KWAKCmBgYHtyIGxvYWQgZGF0YXNldHMgLSB3ZSB3aWxsIGJlIGRvaW5nIHRoYXQgZm9yIGFsbCBydW5zfQoKCgphbGwuYXN2cyAgICAgPC0gbGlzdC5maWxlcyAocGF0aCA9Ii4uIiwgcGF0dGVybiA9ICJeQVNWX3RhYmxlX3IiLCByZWN1cnNpdmUgPSBULCBmdWxsLm5hbWVzID0gVCkKYWxsLm1ldGFkYXRhIDwtIGxpc3QuZmlsZXMgKHBhdGggPSIuLiIsIHBhdHRlcm4gPSAiXm1ldGFkYXRhX3IiLCByZWN1cnNpdmUgPSBULCBmdWxsLm5hbWVzID0gVCkKYWxsLmhhc2hlcyAgIDwtIGxpc3QuZmlsZXMgKHBhdGggPSIuLiIsIHBhdHRlcm4gPSAiXkhhc2hfa2V5X3IiLCByZWN1cnNpdmUgPSBULCBmdWxsLm5hbWVzID0gVCxpZ25vcmUuY2FzZSA9IFQpCgpBU1YudGFibGUgPC0gYmluZF9yb3dzKG1hcChhbGwuYXN2cywgcmVhZF9jc3YpLCAuaWQgPSAiTWlzZXFfcnVuIikKCm1ldGFkYXRhIDwtIGJpbmRfcm93cyhtYXAoYWxsLm1ldGFkYXRhLCBmdW5jdGlvbih4KSB7CiAgcmVhZF9jc3YoeCkgJT4lCiAgICBkcGx5cjo6c2VsZWN0KCJzYW1wbGVfaWQiLCAicHJpX2luZGV4X25hbWUiLCAiVGFnIj0gInNlY19pbmRleF9uYW1lIikKICB9KSwuaWQgPSAiTWlzZXFfcnVuIikKCkhhc2gua2V5IDwtIGJpbmRfcm93cyhtYXAoYWxsLmhhc2hlcywgcmVhZF9jc3YpKQoKSGFzaC5rZXkgJT4lIAogIGRpc3RpbmN0KEhhc2gsIC5rZWVwX2FsbCA9IFQpIC0+IEhhc2gua2V5CmBgYAoKCiMjIERhdGEgQ2xlYW51cCAtIERvbid0IGFjdCBsaWtlIHlvdSBkb24ndCBuZWVkIHRoaXMKCkVtaWx5IGFscmVhZHkgY2xlYW5ldXAgdXAgdGhlIGRhdGEuIEEgZmV3IHRoaW5ncyB3ZSBjaGVjayBmb3I6IFRoYXQgKipubyBzYW1wbGUgYXBwZWFycyB0d2ljZSoqIGluIHRoZSBtZXRhZGF0YS4gVGhhdCB0aGUgbWV0YWRhdGEgKip1c2VzIFRhZ18wMSBpbnN0ZWFkIG9mIFRhZ18xKiogKHNvIGl0IGNhbiBiZSBzb3J0ZWQgYWxwaGFiZXRpY2FsbHkpLiBUaGF0ICoqdGhlIHN0cnVjdHVyZSoqIFNpdGVfWVlZWU1NW0EtQ10uWzEtM10gKippcyB0aGUgc2FtZSoqIGFjcm9zcyB0aGUgZGF0YXNldC4KCmBgYHtyIGRhdGEgY2xlYW5pbmd9CgojIENoZWNrIHRoYXQgbm8gc2FtcGxlIGFwcGVhcnMgbW9yZSB0aGFuIG9uY2UgaW4gdGhlIG1ldGFkYXRhCgptZXRhZGF0YSAlPiUgCiAgZ3JvdXBfYnkoc2FtcGxlX2lkKSAlPiUKICBzdW1tYXJpc2UodG90ID0gbigpKSAlPiUgCiAgYXJyYW5nZShkZXNjKHRvdCkpICMgU2FtcGxlcyBvbmx5IGFwcGVhciBvbmNlCgoKIyBXZSBzaG91bGQgY2hhbmdlIFRhZ18xIGZvciBUYWdfMDEKCm1ldGFkYXRhICU+JQogIG11dGF0ZShUYWcgPSBjYXNlX3doZW4oc3RyX2RldGVjdChUYWcsICJcXF9bMC05XXsxfSQiKSAgICAgICB+ICAgICBzdHJfcmVwbGFjZShUYWcsICJUYWdfIiwgIlRhZ18wIiksCiAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH4gICAgIFRhZyAgKSkgLT4gbWV0YWRhdGEKCgoKbWV0YWRhdGEgJT4lIG11dGF0ZSh4PSBzdHJfY291bnQoc2FtcGxlX2lkLCBwYXR0ZXJuID0gIl8iKSkgJT4lIGFycmFuZ2UoZGVzYyh4KSkgIyBNYXggbnVtYmVyIG9mIHVuZGVyc2NvcmVzIGlzIDUKCm1ldGFkYXRhICU+JSBtdXRhdGUoeD0gc3RyX2NvdW50KHNhbXBsZV9pZCwgcGF0dGVybiA9ICJfIikpICU+JSBhcnJhbmdlKCh4KSkgIyBNaW4gbnVtYmVyIG9mIHVuZGVyc2NvcmVzIGlzIGFsc28gNQoKICAgCkFTVi50YWJsZSAlPiUgZGlzdGluY3Qoc2FtcGxlKSAlPiUgbXV0YXRlKHg9IHN0cl9jb3VudChzYW1wbGUsIHBhdHRlcm4gPSAiXyIpKSAlPiUgYXJyYW5nZShkZXNjKHgpKSAjIEFsc28gaW4gdGhlIEFTViB0YWJsZQoKQVNWLnRhYmxlICU+JSBkaXN0aW5jdChzYW1wbGUpICU+JSBtdXRhdGUoeD0gc3RyX2NvdW50KHNhbXBsZSwgcGF0dGVybiA9ICJfIikpICU+JSBhcnJhbmdlKCh4KSkgIyBBbHNvIGluIHRoZSBBU1YgdGFibGUKCmBgYAoKTm93IGxldCdzIGNoZWNrIHRoZSBwb3NpdGl2ZSBjb250cm9sczogYXJlIGFsbCBmb3IgZWFjaCBydW4ga2FuZ2Fyb29zIG9yIGFsc28gb3N0cmljaGVzCmBgYHtyIENoZWtpbmcgS3MgYW5kIE9zfQoKbWV0YWRhdGEgJT4lIAogIGZpbHRlcihzdHJfZGV0ZWN0KHNhbXBsZV9pZCwgIk5BIikpCgpgYGAKTG9va2luZyBhdCB0aGUgbWV0YWRhdGEgLSBpdCB1c2VkIE9zdHJpY2hlcyBpbiBSb3VuZCAxIGFuZCBLYW5nYXJvbyBpbiByb3VuZHMgMi01CgoKVGhlIG91dHB1dCBvZiB0aGlzIHByb2Nlc3MgYXJlIGEgY2xlYW4gQVNWIHRhYmxlIGFuZCBhIGNsZWFuIG1ldGFkYXRhIGZpbGUuCgojIyBDbGVhbmluZyBQcm9jZXNzIDE6IEVzdGltYXRpb24gb2YgKlRhZy1qdW1waW5nKiBvciBzYW1wbGUgKmNyb3NzLXRhbGsqCgpCZWZvcmUgd2UgbW9kaWZ5IG91ciBkYXRhc2V0cyBvbiBhbnkgd2F5LCB3ZSBjYW4gY2FsY3VsYXRlIGhvdyBtYW55IHNlcXVlbmNlcyB0aGF0IHdlcmUgb25seSBzdXBwb3NlZCB0byBiZSBpbiB0aGUgcG9zaXRpdmVzIGNvbnRyb2wgYXBwZWFyZWQgaW4gdGhlIGVudmlyb25tZW50YWwgc2FtcGxlcywgYW5kIGhvdyBtYW55IGRpZCB0aGUgb3Bwb3NpdGUuIEZpcnN0IHdlIGRpdmlkZSB0aGUgZGF0YXNldCBpbnRvIHBvc2l0aXZlIGNvbnRyb2wgYW5kIGVudmlyb25tZW50YWwgc2FtcGxlcy4gQWxzbyBjcmVhdGUgYW4gb3JkZXJlZCBsaXN0IG9mIHRoZSBIYXNoZXMgcHJlc2VudCBpbiB0aGUgcG9zaXRpdmUgY29udHJvbHMsIGZvciBlYXNlIG9mIHBsb3R0aW5nCgpgYGB7ciBzcGxpdCBpbnRvIHR3b30KCkFTVi50YWJsZSAlPiUgIG11dGF0ZShzb3VyY2UgPSBjYXNlX3doZW4oc3RyX2RldGVjdChzYW1wbGUsICJOQV8iKSAgICB+ICAgIlBvc2l0aXZlcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH4gICAiU2FtcGxlcyIpKSAtPiBBU1YudGFibGUKCkFTVi50YWJsZSAlPiUgCiAgZmlsdGVyIChzb3VyY2UgPT0gIlBvc2l0aXZlcyIpICU+JSAKICBncm91cF9ieShIYXNoKSAlPiUgCiAgc3VtbWFyaXNlKHRvdCA9IHN1bShuUmVhZHMpKSAlPiUgCiAgYXJyYW5nZShkZXNjKHRvdCkpICU+JSAKICBwdWxsKEhhc2gpIC0+IGxpc3Qub2YuaGFzaGVzLmluLnBvc2l0aXZlCgoKYGBgCgpOb3cgbGV0J3MgY3JlYXRlIGEganVtcGluZyB2ZWN0b3IuIFdoYXQgcHJvcG9ydGlvbiBvZiB0aGUgcmVhZHMgZm91bmQgaW4gdGhlIHBvc2l0aXZlcyBjb250cm9sIGNhbWUgZnJvbSBlbHNld2hlcmU/LCBhbmQgd2hhdCBwcm9wb3J0aW9uIG9mIHRoZSByZWFkcyBpbiB0aGUgc2FtcGxlcyBjYW1lIGZyb20gdGhlIHBvc2l0aXZlcyBjb250cm9sPwoKIyMjIFN0ZXAgMTogTmVzdCB0aGUgZGF0YXNldCBhbmQgc3BsaXQgaXQgaW4gcG9zaXRpdmVzIGFuZCBzYW1wbGVzCgpUbyBzdHJlYW1saW5lIHRoZSBwcm9jZXNzIGFuZCBtYWtlIGl0IGVhc2llciB0byBleGVjdXRlIGl0IHNpbWlsYXJseSBidXQgaW5kZXBlbmRlbnRseSBvbiBlYWNoIE1pc2VxIHJ1biwgd2UgbmVzdCB0aGUgZGF0YXNldCBieSBydW4uIApTbyBTdGVwMSBpcyBjcmVhdGUgYSBuZXN0ZWQgdGFibGUgc28gd2UgY2FuIHJ1biB0aGlzIGFuYWx5c2lzIG9uIGVhY2ggcnVuIGluZGVwZW5kZW50bHkuIAoKCmBgYHtyIG5lc3RpbmcgdGhlIGRhdGFzZXR9CgpBU1YudGFibGUgJT4lIAogIGdyb3VwX2J5KE1pc2VxX3J1biwgc291cmNlKSAlPiUgCiAgbmVzdCgpICU+JSAKICBzcHJlYWQoc291cmNlLCBkYXRhKSAtPiBBU1YubmVzdGVkIApgYGAKClRoYXQgd2Fzbid0IHRvbyBjb21wbGljYXRlZC4gTGV0J3Mgc3RhcnQgYSBzdW1tYXJ5IGZ1bmN0aW9uIHRoYXQga2VlcHMgdHJhY2sgb2Ygb3VyIGNsZWFuaW5nIHByb2Nlc3MKCmBgYHtyIHN1bW1hcnkuZmlsZX0KCmhvdy5tYW55IDwtIGZ1bmN0aW9uKEFTVnRhYmxlLCByb3VuZCl7CiAgQVNWdGFibGUgJT4lIHVuZ3JvdXAoKSAlPiUgCiAgICBzdW1tYXJpc2UobnNhbXBsZXMgPSBuX2Rpc3RpbmN0KHNhbXBsZSksCiAgICAgICAgICAgICAgbkhhc2hlcyA9IG5fZGlzdGluY3QoSGFzaCksCiAgICAgICAgICAgICAgblJlYWRzID0gc3VtKG5SZWFkcyksIAogICAgICAgICAgICAgIFN0YWdlID0gcGFzdGUwKCJTdGVwXyIsIHJvdW5kKSkgJT4lIAogICAgZ2F0aGVyKHN0YXJ0c193aXRoKCJuIiksIHZhbHVlID0gIm51bWJlciIsIGtleSA9ICJTdGF0IikKfQoKQVNWLm5lc3RlZCAlPiUgCiAgdHJhbnNtdXRlKE1pc2VxX3J1bixTdW1tYXJ5ID0gbWFwKFNhbXBsZXMsIH4gaG93Lm1hbnkoQVNWdGFibGUgPSAuLHJvdW5kID0gMCkpKSAgLT4gQVNWLnN1bW1hcnkKCmBgYAoKIyMjIFN0ZXAgMjogTW9kZWwgdGhlIGNvbXBvc2l0aW9uIG9mIHRoZSBwb3NpdGl2ZSBjb250cm9scyBvZiBlYWNoIHJ1biAKCgpXZSBjcmVhdGUgYSB2ZWN0b3Igb2YgdGhlIGNvbXBvc2l0aW9uIG9mIGVhY2ggcG9zaXRpdmUgY29udHJvbCBhbmQgc3Vic3RyYWN0IGl0IGZyb20gdGhlIGVudmlyb25tZW50YWwgc2FtcGxlcyBmcm9tIHRoZWlyIHJ1bnMKCgoKYGBge3IganVtcGluZyB2ZWN0b3J9CgoKQVNWLm5lc3RlZCAlPiUgCiAgbXV0YXRlIChjb250YW0udGliYmxlID0gbWFwKFBvc2l0aXZlcywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uKC54KXsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAueCAlPiUgICAgICAgICAgICAgICAgICAgICMgVGFrZSB0aGUgdGliYmxlIGZyb20gZWFjaCBydW4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwX2J5KHNhbXBsZSkgJT4lICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXV0YXRlIChUb3RhbFJlYWRzcGVyU2FtcGxlID0gc3VtKG5SZWFkcykpICU+JSAgICAgICAgICAjIEhvdyBtYW55IHJlYWRzIHBlciBzYW1wbGUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGUgKHByb3BvcnRpb24gPSBuUmVhZHMvVG90YWxSZWFkc3BlclNhbXBsZSkgJT4lICAgICMgV2hhdCBwcm9wb3J0aW9uIGRvZXMgZWFjaCBIYXNoIHJlcHJlc2VudAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXBfYnkgKEhhc2gpICU+JQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtbWFyaXNlICh2ZWN0b3JfY29udGFtaW5hdGlvbiA9IG1heCAocHJvcG9ydGlvbikpICAgICAjIFdoYXQgaXMgdGhlIG1heGltdW0gcHJvcG9ydGlvbiB0aGF0IEhhc2ggZXZlciBnZXRzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSkgKSAtPiBBU1YubmVzdGVkCgpBU1YubmVzdGVkICU+JSAKICB1bm5lc3QoY29udGFtLnRpYmJsZSkgJT4lIAogIGdyb3VwX2J5KE1pc2VxX3J1bikgJT4lIAogIHN1bW1hcmlzZShuaGFzaCA9IG5fZGlzdGluY3QoSGFzaCkpIyBDaGVjayBob3cgaXQgbG9va3MgbGlrZQoKCgpgYGAKCgojIyMgU3RlcCAzOiBTdWJzdHJhY3QgdGhlIGNvbXBvc2l0aW9uIG9mIHRoZSBwb3NpdGl2ZSBjb250cm9scyBmcm9tIHRoZSBlbnZpcm9ubWVudCBzYW1wbGVzCgpUaGUgaWRlYSBiZWhpbmQgdGhpcyBwcm9jZWR1cmUgaXMgdGhhdCB3ZSBrbm93LCBmb3IgZWFjaCBydW4sIGhvdyBtYW55IHJlYWRzIGZyb20gZWFjaCBIYXNoIGFwcGVhcmVkIGluIHRlaCBwb3NpdGl2ZSBjb250cm9scy4gVGhlc2UgY29tZSBmcm9tIDIgcHJvY2Vzc2VzOiBzZXF1ZW5jZXMgd2Uga25vdyBzaG91bGQgYXBwZWFyIGluIHRoZSBwb3NpdGl2ZSBjb250cm9scywgYW5kIHNlcXVlbmNlcyB0aGF0IGhhdmUgKmp1bXBlZCogZnJvbSB0aGUgZW52aXJvbm1lbnQgdG8gdGhlIHBvc2l0aXZlIGNvbnRyb2xzLiBXaXRoIHRoaXMgcHJvY2VkdXJlLCB3ZSBzdWJzdHJhY3QgZnJvbSBldmVyeSBlbnZpcm9ubWVudGFsIHNhbXBsZSB0aGUgcHJvcG9ydGlvbiBvZiByZWFkcyB0aGF0IGp1bXBlZCBmcm9tIGVsc2V3aGVyZS4KCmBgYHtyIGNsZWFuaW5nIHN0ZXAgMX0KQVNWLm5lc3RlZCAlPiUgCiAgbXV0YXRlKGNsZWFuZWQudGliYmxlID0gbWFwMihTYW1wbGVzLCBjb250YW0udGliYmxlLCBmdW5jdGlvbigueCwueSl7ICAjIFdlIG5lZWQgdHdvIGNvbHVtbnM6IHRoZSBzYW1wbGUgZGF0YSBhbmQgdGhlIGNvbnRhbWluYXRpb24gdGliYmxlCiAgICAueCAlPiUgICAgICAjIEdldCB0aGUgc2FtcGxlcyB0aWJibGUKICAgICAgCiAgICAgIGdyb3VwX2J5IChzYW1wbGUpICU+JQogICAgICAKICAgICAgbXV0YXRlIChUb3RhbFJlYWRzcGVyU2FtcGxlID0gc3VtIChuUmVhZHMpKSAlPiUgIyBDYWxjdWxhdGUgdGhlIG51bWJlciBvZiByZWFkcyBwZXIgc2FtcGxlCiAgICAgIAogICAgICBsZWZ0X2pvaW4oLnksIGJ5ID0gIkhhc2giKSAgJT4lICAgICAgICAgICAgICAgICAgIyBBZGQsIGZvciBlYWNoIEhhc2ggc2hhcmVkIHdpdGggdGhlIHBvc2l0aXZlcywgdGhlIHZhbHVlIG9mIHRoZSBjb250YW1pbmF0aW9uIHZlY3RvcgogICAgICAjIAogICAgICAgbXV0YXRlIChVcGRhdGVkX25SZWFkcyA9IGNhc2Vfd2hlbiAoIWlzLm5hKHZlY3Rvcl9jb250YW1pbmF0aW9uKSAgICB+IChhcy5udW1lcmljKG5SZWFkcykgLSAoY2VpbGluZyh2ZWN0b3JfY29udGFtaW5hdGlvbipUb3RhbFJlYWRzcGVyU2FtcGxlKSkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSAgICAgICAgICAgICAgICAgICAgICAgICB+IGFzLm51bWVyaWMoblJlYWRzKSkpICAlPiUgIyBJZiB0aGUgaGFzaCBpcyBwcmVzZW50IGluIHRoZSBwb3NpdGl2ZXMsIHN1YnN0cmFjdCB0aGUgdmFsdWUgb2YgdGhlIHZlY3RvciB0aW1lcyB0aGUgdG90YWwgc2FtcGxlIGRlcHRoLCBvdGhlcndpc2UsIGxlYXZlIGl0IGFzIGl0IGlzCiAgICAgIGZpbHRlciAoVXBkYXRlZF9uUmVhZHMgPiAwKSAlPiUgICAgICAgIyBLZXBwIG9ubHkgdmFsdWVzID4gMAogICAgICB1bmdyb3VwKCkgJT4lCiAgICAgIGRwbHlyOjpzZWxlY3QgKHNhbXBsZSwgSGFzaCwgblJlYWRzID0gVXBkYXRlZF9uUmVhZHMpICAjIExlYXZlIG5vIHRyYWNlCiAgICAgIAogICAgCiAgfSkpIC0+IEFTVi5uZXN0ZWQKCkFTVi5uZXN0ZWQgJT4lIAogIHVubmVzdChjbGVhbmVkLnRpYmJsZSkgI0NoZWNrIGhvdyB0aGV5IGxvb2sKCgpgYGAKQWRkIHRoaXMgc3RlcCB0byB0aGUgc3VtbWFyeSB0YWJsZSB3ZSB3ZXJlIGNyZWF0aW5nCgpgYGB7ciBzdW1tYXJ5LmZpbGUuMn0KQVNWLm5lc3RlZCAlPiUgCiAgdHJhbnNtdXRlKE1pc2VxX3J1biwgU3VtbWFyeS4xID0gbWFwKGNsZWFuZWQudGliYmxlLCB+IGhvdy5tYW55KEFTVnRhYmxlID0gLixyb3VuZCA9ICIxLkp1bXAiKSkpICU+JSAKICBsZWZ0X2pvaW4oQVNWLnN1bW1hcnkpICU+JSAKICBtdXRhdGUoU3VtbWFyeSAgID0gbWFwMihTdW1tYXJ5LCBTdW1tYXJ5LjEsIGJpbmRfcm93cykpICU+JQogIGRwbHlyOjpzZWxlY3QoLVN1bW1hcnkuMSkgLT4gQVNWLnN1bW1hcnkgCgojIENoZWNrIGhvdyBkb2VzIGl0IGxvb2sKCkFTVi5zdW1tYXJ5ICU+JSAKICB1bm5lc3QoU3VtbWFyeSkKCmBgYAoKIyMgQ2xlYW5pbmcgUHJvY2VzcyAyOiAqKkRpc2NhcmRpbmcgUENSIHJlcGxpY2F0ZXMgd2l0aCBsb3cgbnVtYmVyIG9mIHJlYWRzKioKCldlIHdpbGwgZml0IHRoZSBudW1iZXIgb2YgcmVhZHMgYXNzaWduZWQgdG8gZWFjaCBzYW1wbGUgdG8gYSBub3JtYWwgZGlzdHJpYnV0aW9uIGFuZCBkaXNjYXJkIHRob3NlIHNhbXBsZXMgd2l0aCBhIHByb2JhYmlsaXR5IG9mIDk1JSBvZiBub3QgZml0dGluZyBpbiB0aGF0IGRpc3RyaWJ1dGlvbi4gVGhlIG91dHB1dCB3b3VsZCBiZSBhIGRhdGFzZXQgd2l0aCBsZXNzIHNhbXBsZXMgYW5kIHBvdGVudGlhbGx5IGxlc3MgbnVtYmVyIG9mIHVuaXF1ZSBIYXNoZXMuCgpgYGB7ciBmaXR0aW5nIG5SZWFkcyBwZXIgc2FtcGxlfQoKQVNWLm5lc3RlZCAlPiUgCiAgdW5uZXN0KGNsZWFuZWQudGliYmxlKSAlPiUgCiAgZ3JvdXBfYnkoc2FtcGxlKSAlPiUKICBzdW1tYXJpc2UodG90ID0gc3VtKG5SZWFkcykpIC0+IGFsbC5yZXBzCgojIFZpc3VhbGl6ZQoKYWxsLnJlcHMgJT4lICAKICBwdWxsKHRvdCkgLT4gcmVhZHMucGVyLnNhbXBsZQoKbmFtZXMocmVhZHMucGVyLnNhbXBsZSkgPC0gYWxsLnJlcHMgJT4lIHB1bGwoc2FtcGxlKSAgCgpub3JtcGFyYW1zLnJlYWRzIDwtIE1BU1M6OmZpdGRpc3RyKHJlYWRzLnBlci5zYW1wbGUsICJub3JtYWwiKSRlc3RpbWF0ZSAgICMgRml0IHRoZSBudW1iZXIgb2YgcmVhZHMgcGVyIHNhbXBsZSB0byBhIG5vcm1hbCBkaXN0cmlidXRpb24KCgoKYWxsLnJlcHMgJT4lICAKICBtdXRhdGUocHJvYiA9IHBub3JtKHRvdCwgbm9ybXBhcmFtcy5yZWFkc1sxXSwgbm9ybXBhcmFtcy5yZWFkc1syXSkpIC0+IGFsbC5yZXBzICAjIFByb2JhYmlsaXR5IG9mIGVhY2ggZGVwdGggdG8gY29tZSBmcm9tIHRlaCBzYW1lIGRpc3RyaWJ1dGlvbgoKCgpvdXRsaWVycyA8LSAKICBhbGwucmVwcyAlPiUgCiAgZmlsdGVyKHByb2IgPCAwLjA1ICYgdG90IDwgbm9ybXBhcmFtcy5yZWFkc1sxXSkgICAgIyBXaGljaCBzYW1wbGUgYXJlIGJlbG93IDAuMQoKb3V0bGllcnMgJT4lIAogIGFycmFuZ2UodG90KSAjIFNvbWUgaGF2ZSBhcyBmZXcgYXMgNGsKICAKb3V0bGllcnMgJT4lIAogIGFycmFuZ2UoZGVzYyh0b3QpKSAgIyBNYXggbnVtYmVyIG9mIHJlYWRzIG9mIGEgc2FtcGxlIHJlbW92ZWQgaXMgMjBrCgoKQVNWLm5lc3RlZCAlPiUgCiAgbXV0YXRlKFN0ZXAuMS5sb3cucmVhZHMgPSBtYXAgKGNsZWFuZWQudGliYmxlLCB+IGZpbHRlciguLCFzYW1wbGUgJWluJSBvdXRsaWVycyRzYW1wbGUpICU+JSB1bmdyb3VwKSkgLT4gQVNWLm5lc3RlZCAjIFJlbW92ZSBvdXRsaWVycwoKQVNWLm5lc3RlZCAlPiUgCiAgdHJhbnNtdXRlKE1pc2VxX3J1biwgU3VtbWFyeS4xID0gbWFwKFN0ZXAuMS5sb3cucmVhZHMsIH4gaG93Lm1hbnkoQVNWdGFibGUgPSAuLHJvdW5kID0gIjIuTG93Lm5SZWFkcyIpKSkgJT4lIAogIGxlZnRfam9pbihBU1Yuc3VtbWFyeSkgJT4lIAogIG11dGF0ZShTdW1tYXJ5ICAgPSBtYXAyKFN1bW1hcnksIFN1bW1hcnkuMSwgYmluZF9yb3dzKSkgJT4lCiAgZHBseXI6OnNlbGVjdCgtU3VtbWFyeS4xKSAtPiBBU1Yuc3VtbWFyeSAKCmBgYAoKCgoKIyMgQ2xlYW5pbmcgUHJvY2VzcyAzOiAqKkZ1bGwgY2xlYXJhbmNlIGZyb20gUG9zaXRpdmUgY29udHJvbCBpbmZsdWVuY2UqKgoKUmVtb3ZpbmcgdGhlIEhhc2hlcyB0aGF0IGJlbG9uZyB0byB0aGUgcG9zaXRpdmUgY29udHJvbHMuIEZpcnN0LCBmb3IgZWFjaCBIYXNoIHRoYXQgYXBwZWFyZWQgaW4gdGhlIHBvc2l0aXZlIGNvbnRyb2xzLCBkZXRlcm1pbmUgd2hldGhlciBhIHNlcXVlbmNlIGlzIGEgdHJ1ZSBwb3NpdGl2ZSBvciBhIHRydWUgZW52aXJvbm1lbnQuIEZvciBlYWNoIEhhc2gsIHdlIHdpbGwgY2FsY3VsYXRlLCBtYXhpbXVtLCBtZWFuIGFuZCB0b3RhbCBudW1iZXIgb2YgcmVhZHMgaW4gYm90aCBwb3NpdGl2ZSBhbmQgc2FtcGxlcywgYW5kIHRoZW4gd2Ugd2lsbCB1c2UgdGhlIGZvbGxvd2luZyBkZWNpc3Npb24gdHJlZToKCiAgKiBJZiBhbGwgdGhyZWUgc3RhdGlzdGljcyBhcmUgaGlnaGVyIGluIG9uZSBvZiB0aGUgZ3JvdXBzLCB3ZSB3aWxsIGxhYmVsIGl0IGVpdGhlciBvZiBFbnZpcm9ubWVudGFsIG9yIFBvc2l0aXZlIGNvbnRyb2wgaW5mbHVlbmNlLgogIAogICogSWYgdGhlcmUgYXJlIGNvbmZsaWN0aW5nIHJlc3VsdHMsIHdlIHdpbGwgdXNlIHRoZSBIYXNoZXMuIHRvIHNlZSBpZiB0aGV5IGJlbG9uZyB0byBlaXRoZXIgdGhlIG1heGltdW0gYWJ1bmRhbmNlIG9mIGEgSGFzaCBpcyBpbiBhIHBvc2l0aXZlLCB0aGVuIGl0IGlzIGEgcG9zaXRpdmUsIG90aGVyd2lzZSBpcyBhIHJlYWwgc2VxdWVuY2UgZnJvbSB0aGUgZW52aXJvbm1lbnQuCgoKTm93LCBmb3IgZWFjaCBIYXNoIGluIGVhY2ggc2V0IG9mIHBvc2l0aXZlcyBjb250cm9scywgY2FsY3VsYXRlIHRoZSBwcm9wb3J0aW9uIG9mIHJlYWRzIHRoYXQgd2VyZSBtaXNzYXNpZ25lZCAtIHRoZXkgYXBwZWFyZWQgc29tZXdoZXJlIHRoZXkgd2VyZSBub3QgZXhwZWN0ZWQuCldlIHdpbGwgZGl2aWRlIHRoYXQgcHJvY2VzcyBpbiB0d286IGZpcnN0IC4gQSBzZWNvbmQgc3RlcCB3b3VsZCBiZSB0byBjcmVhdGUgYSBjb2x1bW4gbmFtZWQgcHJvcG9ydGlvbiBzd2l0Y2hlZCwgd2hpY2ggc3RhdGVzIHRoZSBwcm9wb3J0aW9uIG9mIHJlYWRzIGZyb20gb25lIEhhc2ggdGhhdCBqdW1wZWQgZnJvbSB0aGUgZW52aXJvbm1lbnQgdG8gYSBwb3NpdGl2ZSBjb250cm9sIG9yIHZpY2V2ZXJzYS4gVGhlIGlkZWEgaXMgdGhhdCBhbnkgcHJlc2VuY2UgYmVsb3cgYSB0aHJlc2hvbGQgY2FuIGJlIGFyZ3VhYmx5IGJlbG9uZyB0byB0YWcganVtcGluZy4KCmBgYHtyIHJlYWwgb3IgcG9zaXRpdmV9CgoKQVNWLnRhYmxlICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBUYWtpbmcgdGhlIHdob2xlIGRhdGFzZXQKICBmaWx0ZXIgKEhhc2ggJWluJSBsaXN0Lm9mLmhhc2hlcy5pbi5wb3NpdGl2ZSkgJT4lICAgIyBrZWVwIG9ubHkgdGhvc2UgaGFzaGVzIHRoYXQgaGF2ZSBzaG93biBhdCBsZWFzdCBvbmNlIGluIGEgcG9zaXRpdmUgY29udHJvbAogIGdyb3VwX2J5KHNhbXBsZSkgJT4lIAogIG11dGF0ZSh0b3QucmVhZHMgPSBzdW0oblJlYWRzKSkgJT4lIAogIGdyb3VwX2J5KEhhc2gsc2FtcGxlKSAlPiUgCiAgbXV0YXRlKHByb3AgPSBuUmVhZHMvdG90LnJlYWRzKSAlPiUgCiAgZ3JvdXBfYnkoSGFzaCwgc291cmNlKSAlPiUgCiAgc3VtbWFyaXNlIChtYXguICA9IG1heChwcm9wKSwKICAgICAgICAgICAgIG1lYW4uID0gbWVhbihwcm9wKSwKICAgICAgICAgICAgIHRvdC4gID0gc3VtKG5SZWFkcykpICU+JSAKICBnYXRoZXIoY29udGFpbnMoIi4iKSwgdmFsdWUgPSAibnVtYmVyIiwga2V5ID0gIlN0YXQiKSAlPiUKICBzcHJlYWQoa2V5ID0gInNvdXJjZSIsIHZhbHVlID0gIm51bWJlciIsIGZpbGwgPSAwKSAlPiUgCiAgZ3JvdXBfYnkoSGFzaCwgU3RhdCkgJT4lCiAgbXV0YXRlKG9yaWdpbiA9IGNhc2Vfd2hlbihQb3NpdGl2ZXMgPiBTYW1wbGVzIH4gIlBvc2l0aXZlLmNvbnRyb2wiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSAgICAgICAgICAgICAgICB+ICJFbnZpcm9ubWVudCIpKSAlPiUgCiAgZ3JvdXBfYnkgKEhhc2gpICU+JQogIG11dGF0ZSh0b3QgPSBuX2Rpc3RpbmN0KG9yaWdpbikpIC0+IEhhc2guZmF0ZS5zdGVwMgoKIyBEbyBhbGwgdGhyZWUgbWVhc3VyZW1lbnRzIGFncmVlCgogSGFzaC5mYXRlLnN0ZXAyICU+JSAKICAgZmlsdGVyKHRvdCAhPSAxICkKCiMgTm8gLSByZW1vdmUgb25seSB0aG9zZSBmb3Igd2hpY2ggdGhlIHRocmVlIG1lYXN1cmVtZW50cyBhZ3JlZQogCiAKSGFzaC5mYXRlLnN0ZXAyICU+JSAKICBmaWx0ZXIodG90ID09IDEpICU+JSAKICBncm91cF9ieShIYXNoKSAlPiUgCiAgc3VtbWFyaXNlKG9yaWdpbiA9IHVuaXF1ZShvcmlnaW4pKSAlPiUgCiAgZmlsdGVyKG9yaWdpbiA9PSAiUG9zaXRpdmUuY29udHJvbCIpIC0+IEhhc2hlcy50by5yZW1vdmUuc3RlcDIKCkFTVi50YWJsZSAlPiUgCiAgZ3JvdXBfYnkoc291cmNlLCBIYXNoKSAlPiUgCiAgc3VtbWFyaXNlKG9jdXJyZW5jZXMgPW4oKSkgJT4lIAogIHNwcmVhZChrZXkgPSBzb3VyY2UsIHZhbHVlID0gb2N1cnJlbmNlcywgZmlsbCA9IDApICU+JSAKICAjbGVmdF9qb2luKEhhc2hlcy50by5yZW1vdmUuc3RlcDIpICU+JSAKICAjbXV0YXRlKG9yaWdpbiA9IGNhc2Vfd2hlbihpcy5uYShvcmlnaW4pIH4gIktlcHQiLAogICAjICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgICAgICAgICAgfiAiRGlzY2FyZGVkIikpICU+JSAKICBtdXRhdGUoc2Vjb25kLm9yaWdpbiA9IGNhc2Vfd2hlbihQb3NpdGl2ZXMgPj0gU2FtcGxlcyB+ICJEaXNjYXJkZWQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgICAgICAgICAgICAgICAgIH4gIktlcHQiKSkgJT4lIAogIGZpbHRlcihzZWNvbmQub3JpZ2luID09ICJEaXNjYXJkZWQiKSAlPiUgCiAgZnVsbF9qb2luKEhhc2hlcy50by5yZW1vdmUuc3RlcDIpIC0+IEhhc2hlcy50by5yZW1vdmUuc3RlcDIKCmBgYApJTiBvcmRlciB0byB0cmFpbiBEQURBMiB0byBiZXR0ZXIgZGlzdGluZ3Vpc2ggd2hlbiBwb3NpdGl2ZSBjb250cm9sIHNlcXVlbmNlcyBoYXZlIGFycml2ZWQgaW4gdGhlIGVudmlyb25tZW50LCB3ZSB3aWxsIGtlZXAgdGhlIHNlcXVlbmNlcyBpbiBhIGNzdiBmaWxlCgoKYGBge3IgQVNWcyBmcm9tIHBvc2l0aXZlc30KCkhhc2hlcy50by5yZW1vdmUuc3RlcDIgJT4lIAogIGxlZnRfam9pbihIYXNoLmtleSkgJT4lIAogIHNlbGVjdChIYXNoLCBTZXF1ZW5jZSkgJT4lIAogIHdyaXRlX2NzdigiSGFzaGVzLnRvLnJlbW92ZS5jc3YiKQoKYGBgCgojIyMgUmVtb3ZlIHRoZSBwb3NpdGl2ZSBjb250cm9sIGhhc2hlcyBmcm9tIHRoZSBjb21wb3NpdGlvbiBvZiB0aGUgQVNWcwoKYGBge3IgY2xlYW5pbmcuU3RlcDJ9CgpBU1YubmVzdGVkICU+JSAKICBtdXRhdGUoU3RlcDIudGliYmxlID0gbWFwIChTdGVwLjEubG93LnJlYWRzLCB+IGZpbHRlciguLCFIYXNoICVpbiUgSGFzaGVzLnRvLnJlbW92ZS5zdGVwMiRIYXNoKSAlPiUgdW5ncm91cCkpIC0+IEFTVi5uZXN0ZWQKCnNhdmVSRFMoQVNWLm5lc3RlZCwgZmlsZSA9ICJDbGVhbmluZy5iZWZvcmUuT2NjLm1vZGVsIikKCkFTVi5uZXN0ZWQgPC0gcmVhZFJEUyhmaWxlID0iQ2xlYW5pbmcuYmVmb3JlLk9jYy5tb2RlbCIpCgpBU1YubmVzdGVkICU+JSAKICB0cmFuc211dGUoTWlzZXFfcnVuLCBTdW1tYXJ5LjEgPSBtYXAoU3RlcDIudGliYmxlLCB+IGhvdy5tYW55KEFTVnRhYmxlID0gLixyb3VuZCA9ICIzLlBvc2l0aXZlcyIpKSkgJT4lIAogIGxlZnRfam9pbihBU1Yuc3VtbWFyeSkgJT4lIAogIG11dGF0ZShTdW1tYXJ5ICAgPSBtYXAyKFN1bW1hcnksIFN1bW1hcnkuMSwgYmluZF9yb3dzKSkgJT4lCiAgZHBseXI6OnNlbGVjdCgtU3VtbWFyeS4xKSAtPiBBU1Yuc3VtbWFyeSAKCkFTVi5zdW1tYXJ5ICU+JSAKICB1bm5lc3QoKQpgYGAKCiMjIENsZWFuaW5nIFByb2Nlc3MgNDogKipPY2N1cGFuY3kgbW9kZWxsaW5nKioKCldoYXQgaXMgdGhlIHByb2JhYmlsdHkgb2YgYSB0cnVlIHBvc2l0aXZlIHByZXNlbmNlIG9mIGEgSGFzaCBpbiBhIE1pc2VxIFJ1bi4gV2Ugd2lsbCB1c2UgZUROQSBvY2N1cGFuY3kgbW9kZWxpbmcgdG8gYXNzZXMgd2hldGhlciBhIGhhc2ggaXMgYSByYXJlIHZhcmlhbnQgdGhhdCBzcGlsbGVkIG91dCBvciBhIHRydWUgcHJlc2VuY2UuCgpUaGUgcHJvY2VzcyByZXF1aXJlcyB0byBsb2FkIGV4dHJhIHBhY2thZ2VzLCBjcmVhdGUgc29tZSBtb2RlbCBmaWxlLCBhbmQgZ3JvdXAgdGhlIGhhc2hlcyBieSBSdW4sIGFuZCBiaW9sb2dpY2FsIHJlcGxpY2F0ZSwgc3VtbWFyaXNpbmcgdGhlIGRhdGEgaW4gYSBwcmVzZW5jZSBhYnNlbmNlIGZvcm1hdC4KClRoZSBvY2N1cGFuY3kgbW9kZWwgaXRzZWxmIHdhcyBwZXJmb3JtZWQgaW4gdGhlIFJtYXJrZG93biBmaWxlIGBSamFncy50dW5uaW5nLlJtZGAsIHNvIGhlcmUgd2Ugd2lsbCB1cGxvYWQgdGhlIGNzdiBmaWxlIHRoYXQgY29udGFpbnMgYWxsIHByb2JhYmlsaXR5IG9mIG9jY3VyZW5jZXMgb2YgYWxsIGhhc2hlcyBwZXIgc2l0ZS4gRWFjaCBIYXNoLVNpdGUgY29tYmluYXRpb24gcHJvZHVjZXMgYSBtYXRyaXggb2YgcHJlc2VuY2UgYWJhc2NlbmNlcyB0aGF0IGZlZWRzIHRoZSBtb2RlbCAtIGZvciBzb21lIGNhc2VzIGl0IGlzIGEgMzB4MyBtYXRyaXgsIGZvciBvdGhlcnMgaXQgaXMgYSAzOXgzLiBXZSBzdW1tYXJpc2VkIHRoZSBudW1iZXIgb2Ygb2NjdXJlbmNlcyBpbiBlYWNoIGNhc2UgYW5kIHJ1biBtb2RlbHMgZm9yIGVhY2ggdW5pcXVlIGNhc2UgKHRvIHNhdmUgY29tcHV0aW5nIHRpbWUpLiBFYWNoIHVuaXF1ZSBtb2RlbCB3YXMgcnVuIDEwIHRpbWVzIHRvIGZpbHRlciBvdXQgY2FzZXMgaW4gd2hpY2ggdGhlIG1vZGVsIGNvbnZlcmdlIGludG8gYSBsb2NhbCBtYXhpbWEuCgpTbyB3ZSB3aWxsIGltcG9ydCB0aGUgb2JqZWN0IGBPY2MuZmF0ZS5jc3ZgIGFuZCByZWR1Y2UgdGhlIGRhdGFzZXQgdG8gdGhvc2UgSGFzaGVzIHdpdGggYW4gb2NjID4gMC44CgpgYGB7ciBpbXBvcnRpbmcgT2NjIHJlc3VsdHN9CgpvY2MucmVzdWx0cyA8LSByZWFkX2NzdigiT2NjLmZhdGUuY3N2IikKCm9jYy5yZXN1bHRzICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBtb2RlbCkpICsKICBnZW9tX2hpc3RvZ3JhbSgpCgpvY2MucmVzdWx0cyAlPiUgCiAgbGVmdF9qb2luKEFTVi5uZXN0ZWQgJT4lIAogICAgICAgICAgICAgIHVubmVzdChTdGVwMi50aWJibGUpICU+JSAKICAgICAgICAgICAgICBncm91cF9ieShIYXNoKSAlPiUgCiAgICAgICAgICAgICAgc3VtbWFyaXNlICh0b3QgPSBzdW0oblJlYWRzKSkpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBjdXRfaW50ZXJ2YWwobW9kZWwsIG4gPSAyMCkpKSArCiAgZ2VvbV9jb2woYWVzKHkgPSB0b3QpLCBmaWxsID0gInJlZCIpIAoKYGBgCgpTbyB3ZSB3aWxsIHRocm93IGF3YXkgbW9zdCBvZiB0aGUgSGFzaGVzLCBidXQgd2lsbCBrZWVwIG1vc3Qgb2YgdGhlIHJlYWRzIC0gd2UgYXJlIGdldHRpbmcgaW50byBzb21ldGhpbmcgaGVyZQoKYGBge3IgYWN0dWFsIGZpbHRlcmluZ30KIG9jYy5yZXN1bHRzICU+JSAKICBmaWx0ZXIobW9kZWwgPiAwLjgpICU+JSAKICBwdWxsIChIYXNoKSAtPiB0by5rZWVwCgpBU1YubmVzdGVkICU+JSAKICBtdXRhdGUoU3RlcDMudGliYmxlID0gbWFwIChTdGVwMi50aWJibGUsIH4gZmlsdGVyKC4sSGFzaCAlaW4lIHRvLmtlZXApKSkgLT4gQVNWLm5lc3RlZAoKQVNWLm5lc3RlZCAlPiUgCiAgdHJhbnNtdXRlKE1pc2VxX3J1biwgU3VtbWFyeS4xID0gbWFwKFN0ZXAzLnRpYmJsZSwgfiBob3cubWFueShBU1Z0YWJsZSA9IC4scm91bmQgPSAiNC5PY2N1cGFuY3kiKSkpICU+JSAKICBsZWZ0X2pvaW4oQVNWLnN1bW1hcnkpICU+JSAKICBtdXRhdGUoU3VtbWFyeSAgID0gbWFwMihTdW1tYXJ5LCBTdW1tYXJ5LjEsIGJpbmRfcm93cykpICU+JQogIGRwbHlyOjpzZWxlY3QoLVN1bW1hcnkuMSkgLT4gQVNWLnN1bW1hcnkgCgoKYGBgCgoKIyMgQ2xlYW5pbmcgUHJvY2VzcyA1OiAqKkRpc3NpbWlsYXJpdHkgYmV0d2VlbiBQQ1IgcmVwbGljYXRlcyoqCgpTbywgYSBzZWNvbmQgd2F5IG9mIGNsZWFuaW5nIHRoZSBkYXRhc2V0IGlzIHRvIHJlbW92ZSBzYW1wbGVzIGZvciB3aGljaCB0aGUgZGlzc2ltaWxhcml0eSBiZXR3ZWVuIFBDUiByZXBsaWNhdGVzIGV4Y2VlZHMgdGhlIG5vcm1hbCBkaXN0cmlidXRpb24gb2YgZGlzc2ltaWxhcml0aWVzLgpTb21ldGltZXMgdGhlIHByZXBhcmF0aW9uIG9mIGEgUENSIHJlcGxpY2F0ZSBnb2VzIHdyb25nIGZvciBhIG51bWJlciBvZiByZWFzb25zIC0gdGhhdCBsZWFkcyB0byBhIHBhcnRpY3VsYXIgUENSIHJlcGxpY2F0ZSB0byBiZSBzdWJzdGFudGlhbGx5IGRpZmZlcmVudCB0byB0aGUgb3RoZXIgMi4gSW4gdGhhdCBjYXNlLCB3ZSB3aWxsIHJlbW92ZSB0aGUgUENSIHJlcGxpY2F0ZSB0aGF0IGhhcyBoaWdoZXIgZGlzc2ltaWxhcml0eSB3aXRoIHRoZSBvdGhlciB0d28uCgpUaGUgcHJvY2VzcyBzdGFydHMgYnkgYWRkaW5nIHRoZSBiaW9sb2dpY2FsIGluZm9ybWF0aW9uIHRvIHRoZSBBU1YgdGFibGUsIHRoZW4gZGl2aW5nIHRoZSBkYXRhc2V0IGJ5IHRoZWlyIGJpb2xvZ2ljYWwgcmVwbGljYXRlLiBUaGlzIHdpbGwgYWxzbyByZW1vdmUgYW55IHNhbXBsZSB0aGF0IGlzIG5vdCBpbmNsdWRlZCBpbiB0aGUgbWV0YWRhdGEsIGVnIGNvbWluZyBmcm9tIGEgZGlmZmVyZW50IHByb2plY3QuCgpgYGB7ciBkaXNzaW1pbGFyaXR5IGJldHdlZW4gUENSIHJlcGxpY2F0ZXN9CgpBU1YubmVzdGVkICU+JSAKICB1bm5lc3QoU3RlcDMudGliYmxlKSAlPiUKICBzZXBhcmF0ZShzYW1wbGUsIGludG8gPSBjKDE6NCwicmVwIiwgInJ1biIpLCBzZXAgPSAiXyIsIHJlbW92ZSA9IEYpICU+JQogIHVuaXRlKGAxYCxgMmAsYDNgLGA0YCwgcnVuLCBjb2wgPSAib3JpZ2luYWxfc2FtcGxlIikgLT4gY2xlYW5lZC50aWJibGUKYGBgCgoKYGBge3IgcXVpY2sgY2hlY2t9CiMgZG8gYWxsIHNhbXBsZXMgaGF2ZSBhIG5hbWUKY2xlYW5lZC50aWJibGUgJT4lIAogIGZpbHRlciAoc2FtcGxlID09ICIiKSAjIFlFUwoKIyBkbyBhbGwgb2YgdGhlbSBoYXZlIGFuIG9yaWdpbmFsIHNhbXBsZQpjbGVhbmVkLnRpYmJsZSAlPiUgCiAgZmlsdGVyKG9yaWdpbmFsX3NhbXBsZSA9PSAiIikgIyBZRVMKCiMgZG8gYWxsIG9mIHRoZW0gaGF2ZSBhIEhhc2gKY2xlYW5lZC50aWJibGUgJT4lIAogIGZpbHRlcihpcy5uYShIYXNoKSkgIyBZRVMKCiMgSG93IG1hbnkgc2FtcGxlcywgaG93IG1hbnkgSGFzaGVzCmNsZWFuZWQudGliYmxlICU+JSAKICBzdW1tYXJpc2Uobl9kaXN0aW5jdChzYW1wbGUpLCAjIDMyNQogICAgICAgICAgICBuX2Rpc3RpbmN0KEhhc2gpKSAgICMgMzE2NgoKIyBMZXQncyBjaGVjayB0aGUgbGV2ZWxzIG9mIHJlcGxpY2F0aW9uCgpjbGVhbmVkLnRpYmJsZSAlPiUgCiAgZ3JvdXBfYnkob3JpZ2luYWxfc2FtcGxlKSAlPiUgCiAgc3VtbWFyaXNlKG5yZXAgPSBuX2Rpc3RpbmN0KHNhbXBsZSkpICU+JSAKICAjZmlsdGVyIChucmVwID09IDIpICMgMTMKICBmaWx0ZXIgKG5yZXAgPT0gMSkgIyA0IApgYGAKT2ssIHNvIHRoZXJlIGFyZSA0IGZvciB3aGljaCB3ZSBvbmx5IGhhdmUgMSBQQ1IgcmVwbGljYXRlIGFuZCAxMyBzYW1wbGVzIGZvciB3aGljaCB3ZSBvbmx5IGhhdmUgMiBQQ1IgcmVwbGljYXRlczEuICAgV2Ugd2lsbCBnZXQgcmlkIG9mIHRob3NlIHdpdGggb25seSAxLCBhcyB3ZSBjYW4ndCBlc3RpbWF0ZSB0aGUgUENSIGJpYXMgdGhlcmUuCgpgYGB7ciByZW1vdmUgc2luZ2xlIHJlcGxpY2F0ZXN9CmRpc2NhcmQuMSA8LSBjbGVhbmVkLnRpYmJsZSAlPiUgCiAgZ3JvdXBfYnkob3JpZ2luYWxfc2FtcGxlKSAlPiUgCiAgbXV0YXRlKG5yZXAgPSBuX2Rpc3RpbmN0KHNhbXBsZSkpICU+JSAKICAjZmlsdGVyIChucmVwID09IDIpICMgMjUKICBmaWx0ZXIgKG5yZXAgPT0gMSkgJT4lIAogIGRpc3RpbmN0KHNhbXBsZSkgJT4lIHB1bGwoc2FtcGxlKQoKY2xlYW5lZC50aWJibGUgJT4lIAogIGZpbHRlcighc2FtcGxlICVpbiUgZGlzY2FyZC4xKSAtPiBjbGVhbmVkLnRpYmJsZQpgYGAKCkFueXdheSwgbGV0J3MgaGF2ZSBhIHZpc3VhbCByZXByZXNlbnRhdGlvbiBvZiB0aGUgZGlzc2ltaWxhcml0aWVzIGJldHdlZW4gUENSIHJlcGxpY2F0ZXMsIGJpb2xvZ2ljYWwgcmVwbGljYXRlcyBhbmQgZXZlcnl0aGluZyBlbHNlLgoKYGBge3IgbGV0cyBkbyB0aGUgUENSIHJlcGxpY2F0aW9ufQpjbGVhbmVkLnRpYmJsZSAlPiUKICBncm91cF9ieSAoc2FtcGxlKSAlPiUKICBtdXRhdGUgKFRvdCA9IHN1bShuUmVhZHMpLAogICAgICAgICAgUm93LnN1bXMgPSBuUmVhZHMgLyBUb3QpICU+JSAKICBncm91cF9ieSAoSGFzaCkgJT4lCiAgbXV0YXRlIChDb2xtYXggPSBtYXggKFJvdy5zdW1zKSwKICAgICAgICAgIE5vcm1hbGl6ZWQucmVhZHMgPSBSb3cuc3VtcyAvIENvbG1heCkgLT4gY2xlYW5lZC50aWJibGUKdGliYmxlX3RvX21hdHJpeCA8LSBmdW5jdGlvbiAodGIpIHsKICAKICB0YiAlPiUgCiAgICBncm91cF9ieShzYW1wbGUsIEhhc2gpICU+JSAKICAgIHN1bW1hcmlzZShuUmVhZHMgPSBzdW0oTm9ybWFsaXplZC5yZWFkcykpICU+JSAKICAgIHNwcmVhZCAoIGtleSA9ICJIYXNoIiwgdmFsdWUgPSAiblJlYWRzIiwgZmlsbCA9IDApIC0+IG1hdHJpeF8xCiAgICBzYW1wbGVzIDwtIHB1bGwgKG1hdHJpeF8xLCBzYW1wbGUpCiAgICBtYXRyaXhfMSAlPiUgCiAgICAgIHVuZ3JvdXAoKSAlPiUgCiAgICBkcGx5cjo6c2VsZWN0ICggLSBzYW1wbGUpIC0+IG1hdHJpeF8xCiAgICBkYXRhLm1hdHJpeChtYXRyaXhfMSkgLT4gbWF0cml4XzEKICAgIGRpbW5hbWVzKG1hdHJpeF8xKVtbMV1dIDwtIHNhbXBsZXMKICAgIHZlZ2Rpc3QobWF0cml4XzEpIC0+IG1hdHJpeF8xCn0KCnRpYmJsZV90b19tYXRyaXggKGNsZWFuZWQudGliYmxlKSAtPiBhbGwuZGlzdGFuY2VzLmZ1bGwKCiNuYW1lcyhhbGwuZGlzdGFuY2VzLmZ1bGwpCgoKc3VtbWFyeShpcy5uYShuYW1lcyhhbGwuZGlzdGFuY2VzLmZ1bGwpKSkKYGBgCgpMZXQncyBtYWtlIHRoZSBwYWlyd2Fpc2UgZGlzdGFuY2VzIGEgbG9uZyB0YWJsZQpgYGB7cn0KYXNfdGliYmxlKGFzLm1hdHJpeChhbGwuZGlzdGFuY2VzLmZ1bGwpICwgcm93bmFtZXMgPSAiU2FtcGxlMSIpICU+JSAKICBnYXRoZXIoLVNhbXBsZTEsIGtleSA9ICJTYW1wbGUyIiwgdmFsdWUgPSAidmFsdWUiKSAtPiBhbGwuZGlzdGFuY2VzLm1lbHRlZAoKc3VtbWFyeShpcy5uYShhbGwuZGlzdGFuY2VzLm1lbHRlZCR2YWx1ZSkpCgojIE5vdywgY3JlYXRlIGEgdGhyZWUgdmFyaWFibGVzIGZvciBhbGwgZGlzdGFuY2VzLCB0aGV5IGNvdWxkIGJlIFBDUiByZXBsaWNhdGVzLCBCSU9MIHJlcGxpY2F0ZXMsIG9yIGZyb20gdGhlIHNhbWUgc2l0ZQoKYWxsLmRpc3RhbmNlcy5tZWx0ZWQgJT4lCiAgc2VwYXJhdGUgKFNhbXBsZTEsIGludG8gPSBjKCJTaXRlMSIsICJBIiwgIkIiLCAiTW9udGgxIiwgInJlcCIsICJyb3VuZCIpLCBzZXAgPSAiXyIsIHJlbW92ZSA9IEZBTFNFKSAlPiUKICB1bml0ZSAoU2l0ZTEsTW9udGgxLCBjb2wgPSAiRGF5LnNpdGUxIiwgcmVtb3ZlID0gRkFMU0UpICU+JQogIHVuaXRlIChTaXRlMSwgTW9udGgxLCBBLCBCLCByb3VuZCwgY29sID0gIkJvdHRsZTEiLCByZW1vdmUgPSBGKSAlPiUgCiAgc2VwYXJhdGUgKFNhbXBsZTIsIGludG8gPSBjKCJTaXRlMiIsICJBMiIsICJCMiIsICJNb250aDIiLCAicmVwMiIsICJyb3VuZDIiKSwgc2VwID0gIl8iLCByZW1vdmUgPSBGQUxTRSkgJT4lCiAgdW5pdGUgKFNpdGUyLCBNb250aDIsIGNvbCA9ICJEYXkuc2l0ZTIiLCByZW1vdmUgPSBGQUxTRSkgJT4lCiAgdW5pdGUgKFNpdGUyLCBNb250aDIsIEEyLCBCMiwgcm91bmQyLCBjb2wgPSAiQm90dGxlMiIsIHJlbW92ZSA9IEYpICU+JSAKICBtdXRhdGUgKCBEaXN0YW5jZS50eXBlID0gY2FzZV93aGVuKCBCb3R0bGUxID09IEJvdHRsZTIgfiAiUENSLnJlcGxpY2F0ZXMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERheS5zaXRlMSA9PSBEYXkuc2l0ZTIgfiAiQmlvbC5yZXBsaWNhdGVzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBTaXRlMSA9PSBTaXRlMiB+ICJTYW1lIFNpdGUiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiAiRGlmZmVyZW50IFNpdGUiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApKSAlPiUKICBkcGx5cjo6c2VsZWN0KFNhbXBsZTEgLFNhbXBsZTIgICwgdmFsdWUgLCBEaXN0YW5jZS50eXBlKSAlPiUKICBmaWx0ZXIgKFNhbXBsZTEgIT0gU2FtcGxlMikgLT4gYWxsLmRpc3RhbmNlcy50by5wbG90CgojIENoZWNraW5nIGFsbCB3ZW50IHdlbGwKCnNhcHBseShhbGwuZGlzdGFuY2VzLnRvLnBsb3QsIGZ1bmN0aW9uKHgpIHN1bW1hcnkoaXMubmEoeCkpKQoKYWxsLmRpc3RhbmNlcy50by5wbG90JERpc3RhbmNlLnR5cGUgPC0gYWxsLmRpc3RhbmNlcy50by5wbG90JERpc3RhbmNlLnR5cGUgICU+JSBmY3RfcmVsZXZlbCggIlBDUi5yZXBsaWNhdGVzIiwgIkJpb2wucmVwbGljYXRlcyIsICJTYW1lIFNpdGUiKQoKICBnZ3Bsb3QgKGFsbC5kaXN0YW5jZXMudG8ucGxvdCAsIGFlcyAoZmlsbCA9IERpc3RhbmNlLnR5cGUsIHggPSB2YWx1ZSkpICsKICBnZW9tX2hpc3RvZ3JhbSAocG9zaXRpb24gPSAiZG9kZ2UiLCBzdGF0ID0gJ2RlbnNpdHknLCBhbHBoYSA9IDAuOSkgKwogIyBmYWNldF93cmFwKCB+IERpc3RhbmNlLnR5cGUpICsKICBsYWJzICh4ID0gIlBhaXJ3aXNlIGRpc3NpbWlsYXJpdHkiLCB5ID0gImRlbnNpdHkiICwKICAgICAgICBEaXN0YW5jZS50eXBlID0gIkRpc3RhbmNlIikKICAKYGBgCgoKCmBgYHtyfQojIEluc3RlYWQgb2YgY2hvc2luZyBiYXNlZCBvbiB0aGUgcHcgZGlzdGFuY2VzLCB3ZSBjYW4gZG8gYSBzaW1pbGFyIHRoaW5nIHVzaW5nIHRoZSBkaXN0YW5jZSB0byBjZW50cm9pZAoKIyBGaW5kIG91dCB3aGljaCBzYW1wbGVzIGhhdmUgb25seSB0d28gcGNyIHJlcGxpY2F0ZXMKY2xlYW5lZC50aWJibGUgJT4lIGRwbHlyOjpzZWxlY3QoLU1pc2VxX3J1bikgJT4lIGdyb3VwX2J5KG9yaWdpbmFsX3NhbXBsZSkgJT4lIG5lc3QoKSAtPiBuZXN0ZWQuY2xlYW5pbmcKCm5lc3RlZC5jbGVhbmluZyAlPiUgCiAgbXV0YXRlKG1hdHJpeCA9IG1hcChkYXRhLCB0aWJibGVfdG9fbWF0cml4KSkgLT4gbmVzdGVkLmNsZWFuaW5nCm5lc3RlZC5jbGVhbmluZyAlPiUgbXV0YXRlKG5jb21wYXJpc29ucyA9IG1hcChtYXRyaXgsIGxlbmd0aCkpIC0+IG5lc3RlZC5jbGVhbmluZwogCiAgCmRpc3RfdG9fY2VudHJvaWQgPC0gZnVuY3Rpb24gKHgseSkgewogIGJpb2wgPC0gcmVwKHksIGxlbmd0aCh4KSkKICAKICBpZiAobGVuZ3RoKGJpb2wpID09IDEpIHsKICAgIG91dHB1dCA9IHJlcCh4WzFdLzIsMikKICAgIG5hbWVzKG91dHB1dCkgPC0gYXR0cih4LCAiTGFiZWxzIikKICB9ZWxzZXsgCiAgICAKICBkaXNwZXJzaW9uIDwtIGJldGFkaXNwZXIoeCwgZ3JvdXAgPSBiaW9sKQogIG91dHB1dCA9IGRpc3BlcnNpb24kZGlzdGFuY2VzCiAgfQogIG91dHB1dAogICAgfQoKCm5lc3RlZC5jbGVhbmluZyA8LSBuZXN0ZWQuY2xlYW5pbmcgJT4lIG11dGF0ZSAoZGlzdGFuY2VzID0gbWFwMihtYXRyaXgsIG9yaWdpbmFsX3NhbXBsZSwgZGlzdF90b19jZW50cm9pZCkpCgp1bmxpc3QgKG5lc3RlZC5jbGVhbmluZyRkaXN0YW5jZXMpIC0+IGFsbF9kaXN0YW5jZXMKCgpgYGAKCmBgYHtyfQojbm9ybXBhcmFtcyA8LSBmaXRkaXN0cihhbGxfcGFpcndpc2VfZGlzdGFuY2VzLCAibm9ybWFsIikkZXN0aW1hdGUKbm9ybXBhcmFtcyA8LSBNQVNTOjpmaXRkaXN0cihhbGxfZGlzdGFuY2VzLCAibm9ybWFsIikkZXN0aW1hdGUKIyAgcHJvYnMgPC0gcG5vcm0oYWxsX3BhaXJ3aXNlX2Rpc3RhbmNlcywgbm9ybXBhcmFtc1sxXSwgbm9ybXBhcmFtc1syXSkKcHJvYnMgPC0gcG5vcm0oYWxsX2Rpc3RhbmNlcywgbm9ybXBhcmFtc1sxXSwgbm9ybXBhcmFtc1syXSkKb3V0bGllcnMgPC0gd2hpY2gocHJvYnM+MC45NSkKCmRpc2NhcmQgPC1uYW1lcyAoYWxsX2Rpc3RhbmNlc1tvdXRsaWVyc10pCgphbGxfZGlzdGFuY2VzCnRvX3dyaXRlX2Rpc2NhcmRlZCA8LSB0aWJibGUoc2FtcGxlID0gZGlzY2FyZCwgdmFsdWUgPSBhbGxfZGlzdGFuY2VzW291dGxpZXJzXSkKCnRvX3dyaXRlX2Rpc2NhcmRlZCA8LSB0b193cml0ZV9kaXNjYXJkZWQgJT4lIGJpbmRfcm93cyh0aWJibGUoc2FtcGxlID0gZGlzY2FyZC4xLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpc3RhbmNlX3RvX2NlbnRyb2lkID0gTkEpKQp3cml0ZV9jc3YodG9fd3JpdGVfZGlzY2FyZGVkICwiZGlzY2FyZWRfc2FtcGxlcy5jc3YiKQoKCiAgCiAgCmBgYAoKRmluYWxseSwgbGV0J3MgcmVtb3ZlIHRoZXNlIHNhbXBsZXMgZnJvbSB0aGUgZGF0YXNldAoKYGBge3IgYWN0dWFsIGNsZWFuaW5nfQoKQVNWLm5lc3RlZCAlPiUgCiAgbXV0YXRlKFN0ZXA0LnRpYmJsZSA9IG1hcCAoU3RlcDMudGliYmxlLCAgfiBmaWx0ZXIoLiwhIHNhbXBsZSAlaW4lIHRvX3dyaXRlX2Rpc2NhcmRlZCRzYW1wbGUpKSkgLT4gQVNWLm5lc3RlZAoKQVNWLm5lc3RlZCAlPiUgCiAgdHJhbnNtdXRlKE1pc2VxX3J1biwgU3VtbWFyeS4xID0gbWFwKFN0ZXA0LnRpYmJsZSwgfiBob3cubWFueShBU1Z0YWJsZSA9IC4scm91bmQgPSAiNS5QQ1IuZGlzc2ltaWxhcml0eSIpKSkgJT4lIAogIGxlZnRfam9pbihBU1Yuc3VtbWFyeSkgJT4lIAogIG11dGF0ZShTdW1tYXJ5ICAgPSBtYXAyKFN1bW1hcnksIFN1bW1hcnkuMSwgYmluZF9yb3dzKSkgJT4lCiAgZHBseXI6OnNlbGVjdCgtU3VtbWFyeS4xKSAtPiBBU1Yuc3VtbWFyeSAKYGBgCgoKIyMgRXhwb3J0aW5nIHRoZSBvdXRwdXQKCldlIHdpbGwgZXhwb3J0IHRoZSBmaW5hbCBjbGVhbmVkIHRhYmxlIHdpdGggZm91ciBjb2x1bW5zIChNaXNlcV9ydW4sIHNhbXBsZSwgSGFzaCwgblJlYWRzKQoKYGBge3J9CgpBU1YubmVzdGVkICU+JSAKICB1bm5lc3QoU3RlcDQudGliYmxlKSAlPiUgCiAgbXV0YXRlKG5SZWFkcyA9IGFzLmludGVnZXIoblJlYWRzKSkgJT4lIAogIHdyaXRlX2NzdigiQ2xlYW5fZGF0YS9BU1ZfdGFibGVfYWxsX3RvZ2V0aGVyLmNzdiIpCgpBU1YubmVzdGVkICU+JSAKICB1bm5lc3QoU3RlcDQudGliYmxlKSAlPiUgCiAgZGlzdGluY3QoSGFzaCkgJT4lIAogIGxlZnRfam9pbihIYXNoLmtleSkgJT4lIAogIHdyaXRlX2NzdigiQ2xlYW5fZGF0YS9IYXNoX0tleV9hbGxfdG9nZXRoZXIuY3N2IikKCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHNlcWlucikKCmlucHV0IDwtIHJlYWRfY3N2KCJDbGVhbl9kYXRhL0hhc2hfS2V5X2FsbF90b2dldGhlci5jc3YiKQpvdXRwdXQgPC0gIkNsZWFuX2RhdGEvSGFzaF9LZXlfYWxsX3RvZ2V0aGVyLmZhc3RhIgoKd3JpdGUuZmFzdGEgKHNlcXVlbmNlcyA9IGFzLmxpc3QoaW5wdXQkU2VxdWVuY2UpLAogICAgICAgICAgICAgbmFtZXMgPSBhcy5saXN0KGlucHV0JEhhc2gpLAogICAgICAgICAgICAgZmlsZS5vdXQgPSBvdXRwdXQpCgoKc2F2ZVJEUyhBU1YubmVzdGVkLCBmaWxlID0gIkFMTC5BU1ZzLm5lc3RlZCIpCgoKICAKCmBgYAoKCiMjIENoZWNraW5nIHRoZSBvdXRwdXQKCgpMZXQncyBjaGVjayBvdXQgdGhlIHN1Y2Nlc3Mgb2Ygb3VyIGFwcHJvYWNoIC0gdXNlIHRoZSB0YXhvbm9teSBhbm5vdGF0aW9uIHRvIGxvb2sgZm9yIHZlcnRlYnJhdGUgc2VxdWVuY2VzCgpgYGB7ciBnZXR0aW5nIHRoZSB0YXhvbm9teX0KClRheG9ub215IDwtIHJlYWRfY3N2KCIuLi80LlRheG9ub21pY19hbm5vdGF0aW9uL0ZpbmFsLkNsYXNzaWZpY2F0aW9uLkhhc2hlczIwMTgtMTEtMDYuY3N2IikKTGluZWFnZSA8LSByZWFkX2NzdigiLi4vLi4vQW5hbHlzaXMvQW5hbHlzaXMuZm9yLldTQy9MaW5lYWdlTG9va3VwVGFibGUuZmFtaWxpZXMuY3N2IikKCkFTVi5uZXN0ZWQgJT4lIAogIHVubmVzdChQb3NpdGl2ZXMpICU+JSAjICAgICAgICAgICU+JSBzdW1tYXJpc2Uoc3VtKG5SZWFkcykpICA1ME0gcmVhZHMgcGFzc2VkIGNsZWFuaW5nIGZpbHRlcnMKICBsZWZ0X2pvaW4oVGF4b25vbXkpICU+JSAKICBncm91cF9ieShGYW1pbHksTWlzZXFfcnVuKSAlPiUKICBzdW1tYXJpc2UodG90ID0gc3VtKG5SZWFkcykpICU+JSAKICBmaWx0ZXIgKHN0cl9kZXRlY3QoRmFtaWx5LCAiaWRhZSIpKSAlPiUgCiAgbGVmdF9qb2luKExpbmVhZ2UpICU+JSAKICBhcnJhbmdlKGRlc2MoUGh5bHVtKSkgJT4lIAogIGZpbHRlciAoUGh5bHVtID09ICJDaG9yZGF0YSIpICU+JSAgZHBseXI6OnNlbGVjdCAoRmFtaWx5LE1pc2VxX3J1bix0b3QpIC0+IGxpc3Qub2YuZmFtaWxpZXMuaW4ucG9zaXRpdmVzCiAgCkFTVi5uZXN0ZWQgJT4lIAogIHVubmVzdChTYW1wbGVzKSAlPiUgI2ZpbHRlciAoTWlzZXFfcnVuID09IDEpICU+JSBncm91cF9ieShzYW1wbGUpICU+JSBzdW1tYXJpc2UodG90ID0gc3VtKG5SZWFkcykpICU+JSBmaWx0ZXIgKHRvdCA8IDgwMDAwKSNnZ3Bsb3QgKGFlcyh4ID0gdG90KSkgK2dlb21faGlzdG9ncmFtKGJpbnMgPSA1MCkjICAgICAgICAgICU+JSBzdW1tYXJpc2Uoc3VtKG5SZWFkcykpICA1ME0gcmVhZHMgcGFzc2VkIGNsZWFuaW5nIGZpbHRlcnMKICBsZWZ0X2pvaW4oVGF4b25vbXkpICU+JSAKICBncm91cF9ieShGYW1pbHksTWlzZXFfcnVuKSAlPiUKICBzdW1tYXJpc2UodG90ID0gc3VtKG5SZWFkcykpICU+JSAKICBmaWx0ZXIgKHN0cl9kZXRlY3QoRmFtaWx5LCAiaWRhZSIpKSAlPiUgCiAgbGVmdF9qb2luKExpbmVhZ2UpICU+JSAKICBhcnJhbmdlKGRlc2MoUGh5bHVtKSkgJT4lIAogIGZpbHRlciAoUGh5bHVtID09ICJDaG9yZGF0YSIpICU+JSAgZHBseXI6OnNlbGVjdChGYW1pbHksTWlzZXFfcnVuLCB0b3QpIC0+IGxpc3Qub2YuZmFtaWxpZXMuaW4uc2FtcGxlcwoKQVNWLm5lc3RlZCAlPiUgCiAgdW5uZXN0KFN0ZXAzLnRpYmJsZSkgJT4lICMgICAgICAgICAgJT4lIHN1bW1hcmlzZShzdW0oblJlYWRzKSkgIDUwTSByZWFkcyBwYXNzZWQgY2xlYW5pbmcgZmlsdGVycwogIGxlZnRfam9pbihUYXhvbm9teSkgJT4lIAogIGdyb3VwX2J5KEZhbWlseSxNaXNlcV9ydW4pICU+JQogIHN1bW1hcmlzZSh0b3QgPSBzdW0oblJlYWRzKSkgJT4lIAogIGZpbHRlciAoc3RyX2RldGVjdChGYW1pbHksICJpZGFlIikpICU+JSAKICBsZWZ0X2pvaW4oTGluZWFnZSkgJT4lIAogIGFycmFuZ2UoZGVzYyhQaHlsdW0pKSAlPiUgCiAgZmlsdGVyIChQaHlsdW0gPT0gIkNob3JkYXRhIikgJT4lICBkcGx5cjo6c2VsZWN0KEZhbWlseSxNaXNlcV9ydW4sIHRvdCkgLT4gbGlzdC5vZi5mYW1pbGllcy5pbi5jbGVhbi5zYW1wbGVzCgpsaXN0Lm9mLmZhbWlsaWVzLmluLmNsZWFuLnNhbXBsZXMgJT4lIAogIGdyb3VwX2J5KEZhbWlseSkgJT4lIAogIHN1bW1hcmlzZSh0b3QgPSBzdW0gKHRvdCkpCgpBU1Yuc3VtbWFyeSAlPiUgCiAgdW5uZXN0KCkgJT4lICNzcHJlYWQoU3RhdCwgIG51bWJlcikKICBnZ3Bsb3QoYWVzKHg9U3RhZ2UsIHk9bnVtYmVyLCBmaWxsID0gU3RhdCkpKwogICAgZ2VvbV9saW5lKGFlcyhncm91cCA9IFN0YXQsIGNvbG9yID0gU3RhdCkpKwogICNnZW9tX2JveHBsb3QocG9zaXRpb24gPSAiZG9kZ2UiKSsKICAjZmFjZXRfd3JhcCh+U3RhdCwgc2NhbGVzID0gImZyZWUiKSsKICBmYWNldF9ncmlkKFN0YXR+TWlzZXFfcnVuLCBzY2FsZXMgPSAiZnJlZSIpKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0xKSkjLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKIyBTdW0gc3RhdHMgZm9yIHRoZSBwYXBlciAtIEkgbmVlZCB0byBkbyBpdCBmb3IgYWxsIHJ1bnMgYXQgb25jZSwgd2l0aG91dCBuZXN0aW5nCkFTVi5uZXN0ZWQgJT4lIAogIGZpbHRlciAoTWlzZXFfcnVuICE9IDQpIC0+IEFTVi5uZXN0ZWQudG8ucGFwZXIKCkFTVi5uZXN0ZWQudG8ucGFwZXIgJT4lIAogIHVubmVzdChTYW1wbGVzKSAlPiUgCiAgdW5ncm91cCgpICU+JSAKICBuZXN0KC5rZXkgPSBTYW1wbGVzKSAlPiUgCiAgdHJhbnNtdXRlKFN1bW1hcnkgPSBtYXAoU2FtcGxlcywgfiBob3cubWFueShBU1Z0YWJsZSA9IC4scm91bmQgPSAwKSkpICAtPiBBU1Yuc3VtbWFyeS5hbGwudG9nZXRoZXIKCkFTVi5uZXN0ZWQudG8ucGFwZXIgJT4lIAogIHVubmVzdChjbGVhbmVkLnRpYmJsZSkgJT4lIAogIHVuZ3JvdXAoKSAlPiUgCiAgbmVzdCgua2V5ID0gY2xlYW5lZC50aWJibGUpICU+JSAKICB0cmFuc211dGUoU3VtbWFyeS4xID0gbWFwKGNsZWFuZWQudGliYmxlLCB+IGhvdy5tYW55KEFTVnRhYmxlID0gLixyb3VuZCA9ICIxLkp1bXAiKSkpICU+JSAKICBiaW5kX2NvbHMoQVNWLnN1bW1hcnkuYWxsLnRvZ2V0aGVyKSAlPiUgCiAgbXV0YXRlKFN1bW1hcnkgICA9IG1hcDIoU3VtbWFyeSwgU3VtbWFyeS4xLCBiaW5kX3Jvd3MpKSAlPiUKICBkcGx5cjo6c2VsZWN0KC1TdW1tYXJ5LjEpIC0+IEFTVi5zdW1tYXJ5LmFsbC50b2dldGhlciAKCkFTVi5uZXN0ZWQudG8ucGFwZXIgJT4lIAogIHVubmVzdChTdGVwLjEubG93LnJlYWRzKSAlPiUgCiAgdW5ncm91cCgpICU+JSAKICBuZXN0KC5rZXkgPSBTdGVwLjEubG93LnJlYWRzKSAlPiUgCiAgdHJhbnNtdXRlKFN1bW1hcnkuMSA9IG1hcChTdGVwLjEubG93LnJlYWRzLCB+IGhvdy5tYW55KEFTVnRhYmxlID0gLixyb3VuZCA9ICIyLkxvdy5uUmVhZHMiKSkpICU+JSAKICBiaW5kX2NvbHMoQVNWLnN1bW1hcnkuYWxsLnRvZ2V0aGVyKSAlPiUgCiAgbXV0YXRlKFN1bW1hcnkgICA9IG1hcDIoU3VtbWFyeSwgU3VtbWFyeS4xLCBiaW5kX3Jvd3MpKSAlPiUKICBkcGx5cjo6c2VsZWN0KC1TdW1tYXJ5LjEpIC0+IEFTVi5zdW1tYXJ5LmFsbC50b2dldGhlciAKCkFTVi5uZXN0ZWQudG8ucGFwZXIgJT4lIAogIHVubmVzdChTdGVwMi50aWJibGUpICU+JSAKICB1bmdyb3VwKCkgJT4lIAogIG5lc3QoLmtleSA9IFN0ZXAyLnRpYmJsZSkgJT4lIAogIHRyYW5zbXV0ZShTdW1tYXJ5LjEgPSBtYXAoU3RlcDIudGliYmxlLCB+IGhvdy5tYW55KEFTVnRhYmxlID0gLixyb3VuZCA9ICIzLlBvc2l0aXZlcyIpKSkgJT4lIAogIGJpbmRfY29scyhBU1Yuc3VtbWFyeS5hbGwudG9nZXRoZXIpICU+JSAKICBtdXRhdGUoU3VtbWFyeSAgID0gbWFwMihTdW1tYXJ5LCBTdW1tYXJ5LjEsIGJpbmRfcm93cykpICU+JQogIGRwbHlyOjpzZWxlY3QoLVN1bW1hcnkuMSkgLT4gQVNWLnN1bW1hcnkuYWxsLnRvZ2V0aGVyIAoKQVNWLm5lc3RlZC50by5wYXBlciAlPiUgCiAgdW5uZXN0KFN0ZXAzLnRpYmJsZSkgJT4lIAogIHVuZ3JvdXAoKSAlPiUgCiAgbmVzdCgua2V5ID0gU3RlcDMudGliYmxlKSAlPiUgCiAgdHJhbnNtdXRlKFN1bW1hcnkuMSA9IG1hcChTdGVwMy50aWJibGUsIH4gaG93Lm1hbnkoQVNWdGFibGUgPSAuLHJvdW5kID0gIjQuT2NjdXBhbmN5IikpKSAlPiUgCiAgYmluZF9jb2xzKEFTVi5zdW1tYXJ5LmFsbC50b2dldGhlcikgJT4lIAogIG11dGF0ZShTdW1tYXJ5ICAgPSBtYXAyKFN1bW1hcnksIFN1bW1hcnkuMSwgYmluZF9yb3dzKSkgJT4lCiAgZHBseXI6OnNlbGVjdCgtU3VtbWFyeS4xKSAtPiBBU1Yuc3VtbWFyeS5hbGwudG9nZXRoZXIgCgoKQVNWLm5lc3RlZC50by5wYXBlciAlPiUgCiAgdW5uZXN0KFN0ZXA0LnRpYmJsZSkgJT4lIAogIHVuZ3JvdXAoKSAlPiUgCiAgbmVzdCgua2V5ID0gU3RlcDQudGliYmxlKSAlPiUgCiAgdHJhbnNtdXRlKFN1bW1hcnkuMSA9IG1hcChTdGVwNC50aWJibGUsIH4gaG93Lm1hbnkoQVNWdGFibGUgPSAuLHJvdW5kID0gIjUuUENSLmRpc3NpbWlsYXJpdHkiKSkpICU+JSAKICBiaW5kX2NvbHMoQVNWLnN1bW1hcnkuYWxsLnRvZ2V0aGVyKSAlPiUgCiAgbXV0YXRlKFN1bW1hcnkgICA9IG1hcDIoU3VtbWFyeSwgU3VtbWFyeS4xLCBiaW5kX3Jvd3MpKSAlPiUKICBkcGx5cjo6c2VsZWN0KC1TdW1tYXJ5LjEpIC0+IEFTVi5zdW1tYXJ5LmFsbC50b2dldGhlciAKCkFTVi5zdW1tYXJ5LmFsbC50b2dldGhlciAlPiUgCiAgdW5uZXN0KCkgJT4lIAogIGdncGxvdChhZXMoeD1TdGFnZSwgeT1udW1iZXIsIGZpbGwgPSBTdGF0KSkrCiAgICBnZW9tX2xpbmUoYWVzKGdyb3VwID0gU3RhdCwgY29sb3IgPSBTdGF0KSkgKwogIGZhY2V0X3dyYXAoLn5TdGF0LCBzY2FsZXMgPSAiZnJlZSIpKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0xKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCkpCiAgCgpBU1Yuc3VtbWFyeS5hbGwudG9nZXRoZXIgJT4lIAogIHVubmVzdCgpICU+JSAKICBzcHJlYWQoa2V5ID0gU3RhdCwgdmFsdWUgPSBudW1iZXIpCjIzLjU1ODYzMi8yOC4yODkzMjEKCmBgYAoKYGBge3J9CgpBU1Yuc3VtbWFyeSAlPiUgCiAgdW5uZXN0KCkgJT4lIAogIGZpbHRlciAoTWlzZXFfcnVuIT0gIjQiKSAlPiUgCiAgZ3JvdXBfYnkoU3RhZ2UsIFN0YXQpICU+JQogIHN1bW1hcmlzZShudW1iZXIgPSBzdW0obnVtYmVyKSkgJT4lIAogIHNwcmVhZChrZXkgPSBTdGF0ICwgdmFsdWUgPSBudW1iZXIpCiAgbmVzdCAoKQoKI3doZXJlIGFyZSB0aGUgc2FsbW9ucwoKQVNWLm5lc3RlZCAlPiUgCiAgdW5uZXN0KFN0ZXA0LnRpYmJsZSkgJT4lICMgICAgICAgIAogIGxlZnRfam9pbihUYXhvbm9teSkgJT4lIAogIHNlcGFyYXRlKHNhbXBsZSwgaW50byA9ICJiaW9sIiwgc2VwID0gIlxcLiIsIHJlbW92ZSA9IEYpICU+JSAKICBncm91cF9ieShGYW1pbHksIGJpb2wpICU+JSAKICBzdW1tYXJpc2UgKHRvdCA9IHN1bShuUmVhZHMpKSAlPiUgCiAgZmlsdGVyIChGYW1pbHkgPT0gIlNhbG1vbmlkYWUiKSAlPiUgCiAgYXJyYW5nZShkZXNjKHRvdCkpCgojIFByZXZhbGVuY2UgcGxvdCBhcyBpbiBkZWNvbnRhbSBwYWNrYWdlCgpBU1YudGFibGUgJT4lIAogIGdyb3VwX2J5KHNvdXJjZSwgSGFzaCkgJT4lIAogIHN1bW1hcmlzZShvY3VycmVuY2VzID1uKCkpICU+JSAKICBzcHJlYWQoa2V5ID0gc291cmNlLCB2YWx1ZSA9IG9jdXJyZW5jZXMsIGZpbGwgPSAwKSAlPiUgCiAgbGVmdF9qb2luKEhhc2hlcy50by5yZW1vdmUuc3RlcDIpICU+JSAKICBtdXRhdGUob3JpZ2luID0gY2FzZV93aGVuKGlzLm5hKG9yaWdpbikgfiAiS2VwdCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBUUlVFICAgICAgICAgIH4gIkRpc2NhcmRlZCIpKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gUG9zaXRpdmVzLCB5ID0gU2FtcGxlcywgY29sb3IgPSBvcmlnaW4pKSsKICBnZW9tX3BvaW50KCkKCkFTVi50YWJsZSAlPiUgCiAgZ3JvdXBfYnkoc291cmNlLCBIYXNoKSAlPiUgCiAgc3VtbWFyaXNlKG9jdXJyZW5jZXMgPW4oKSkgJT4lIAogIHNwcmVhZChrZXkgPSBzb3VyY2UsIHZhbHVlID0gb2N1cnJlbmNlcywgZmlsbCA9IDApICU+JSAKICAjbGVmdF9qb2luKEhhc2hlcy50by5yZW1vdmUuc3RlcDIpICU+JSAKICAjbXV0YXRlKG9yaWdpbiA9IGNhc2Vfd2hlbihpcy5uYShvcmlnaW4pIH4gIktlcHQiLAogICAjICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgICAgICAgICAgfiAiRGlzY2FyZGVkIikpICU+JSAKICBtdXRhdGUoc2Vjb25kLm9yaWdpbiA9IGNhc2Vfd2hlbihQb3NpdGl2ZXMgPj0gU2FtcGxlcyB+ICJEaXNjYXJkZWQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgICAgICAgICAgICAgICAgIH4gIktlcHQiKSkgJT4lIAogIGZpbHRlcihzZWNvbmQub3JpZ2luID09ICJEaXNjYXJkZWQiKSAlPiUgCiAgZnVsbF9qb2luKEhhc2hlcy50by5yZW1vdmUuc3RlcDIpIC0+IEhhc2hlcy50by5yZW1vdmUuc3RlcDIKSGFzaGVzLnRvLnJlbW92ZS5zdGVwMiAlPiUgCmZpbHRlciAoSGFzaCA9PSAiYWNlYmNkNWM0OTFiYjI3M2YzZTRkNjE1Y2FmYWQ2NDkiKQpgYGAKCgo=